三個方法解決error: use of moved value


https://www.tuicool.com/articles/UfIvemA

 

2015 年 6 月 4 日

概述

“error: use of moved value”,相信最近開始玩rust的同學看到這個報錯都能會心一笑了。

rust做到了不依賴運行期垃圾回收的安全內存管理,但這個特別爽快的特性也引入了一些不便,這報錯就是常見的麻煩之一。

這報錯要是想展開說清楚,需要完整解釋rust的ownership、borrowing、lifetime等概念,那是一篇太長的文章。

我們先暫時放下概念,用三個不同的方法動手解決這個報錯。

錯誤

我們以下面這段程序為基礎展開我們的討論,這里面主要定義的就是一個Info結構體。

struct Info { s: String, } fn fn_a(info: Info) { println!("in fn_a"); } fn main() { let foo = Info {s : "abc".to_string() }; fn_a(foo); }

首先,我們要制造出報錯“use of moved value”。很簡單,我們只需要以foo為參數再調用一次fn_a()就好。

struct Info { s: String, } fn fn_a(info: Info) { println!("in fn_a"); } fn main() { let foo = Info {s : "abc".to_string() }; fn_a(foo); fn_a(foo); // 只有這行是新加入的 }

現在,我們得到了編譯器報錯。

src/main.rs:12:10: 12:13 error: use of moved value: `foo` src/main.rs:12 fn_a(foo); ^~~ src/main.rs:11:10: 11:13 note: `foo` moved here because it has type `Info`, which is non-copyable src/main.rs:11 fn_a(foo); ^~~

編譯器說,我們新加入的行里用了“移動了的值”。

啥叫“移動了的值”呢?

說白了就是用過了的值,foo已經給第一個fn_a()用過了,到了第二個fn_a()的時候就是moved value了。然后就不讓用了。

至於為什么要制定這樣的規則,我另撰文解釋。

現在我們開始動手來解決這個問題。

方法一:引用

因為我們傳給函數的是value,所以value被move了。

我們可以通過傳引用給函數來解決這問題。

代碼如下:

struct Info { pub s: String, } fn fn_a(info: &mut Info) { info.s = "bbb".to_string(); } fn main() { let mut foo = Info {s : "abc".to_string() }; println!("1: {}", foo.s); fn_a(&mut foo); println!("2: {}", foo.s); fn_a(&mut foo); println!("3: {}", foo.s); }

運行結果如下:

1: abc
2: bbb 3: bbb

我們把這段程序用impl改寫一下。下面這段程序和上面的程序其實是等價的:

struct Info { pub s: String, } impl Info { fn fn_a(&mut self) { self.s = "bbb".to_string(); } } fn main() { let mut foo = Info {s : "abc".to_string() }; println!("1: {}", foo.s); foo.fn_a(); println!("2: {}", foo.s); foo.fn_a(); println!("3: {}", foo.s); }

運行結果顯然是一樣的:

1: abc
2: bbb 3: bbb

改寫成這樣之后是不是很眼熟了呢。

沒錯,大多數的標准庫就是用的這種方法來避免moved value問題的。

impl里面的第一個參數其實就是struct的引用,所以我們用struct.fn()這種寫法的時候,傳遞給方法實際都是引用。

方法二:引用計數

rust在標准庫里提供了引用計數,它是另一個可以解決move value的方法。先列出來代碼吧。

use std::rc::Rc;
use std::cell::RefCell; struct Info { s: String, } impl Info { fn new(a: &str) -> Info { Info { s: a.to_string(), } } } fn abc(a: Rc<RefCell<Info>>) { a.borrow_mut().s = "bbbbb".to_string(); } fn main() { let bar = Rc::new(RefCell::new(Info::new("abc"))); println!("1 : {}", bar.borrow().s); abc(bar.clone()); println!("2 : {}", bar.borrow().s); abc(bar.clone()); println!("3 : {}", bar.borrow().s); }

這段代碼有點稍稍復雜。

其實需要注意的地方只有兩個:

1、使用了Rc之后,傳遞變量都要使用.clone()方法來增加引用計數;減少引用計數不用管,rust會根據作用域自己搞定;

2、變量不用聲明為mut了。使用的時候,如果不更改,使用.borrow()方法得到真正的struct;如果需要更改,則使用.borrow_mut()方法得到真正的struct。

另外還有兩點需要再說明一下:

1、如果變量根本不需要改變,則不用套里面的RefCell::new()那層;

2、如果涉及多線程之間的傳參,要放棄Rc,使用Arc。

方法三:實現Trait Clone

從方法二中可以看到,我們使用了Rc,Rc就幫助我們實現了一個.clone()方法,讓我們得以避免錯誤。

那我們能不能自己實現.clone()呢,答案當然是肯定的。

Rc做的是把數據和計數都放到了堆上,它提供的.clone()實際是對計數的復制。

我們不搞那么復雜,我們做個簡單點的,我們直接做對struct的復制。

代碼如下:

struct Info { s: i32, } impl Info { fn new(a: i32) -> Info { Info { s: a, } } } impl Clone for Info { fn clone(&self) -> Info { Info {s: self.s} } } fn abc(a: Info) -> Info { Info {s: a.s + 1} } fn main() { let mut foo = Info::new(111); println!("1 : {}", foo.s); abc(foo); println!("2 : {}", foo.s); abc(foo); println!("3 : {}", foo.s); }

運行結果:

1 : 111 2 : 111 3 : 111

為什么我們這里沒有寫foo.clone()而直接寫foo編譯器也沒抱怨什么呢?

我們可以看看最初編譯器說的話:note: foo moved here because it has type Info , which is non-copyable

它說,foo因為不能復制,所以它被move了。

所以當我們讓Info變得可復制了之后,它就不會被move了。

當然要寫成foo.clone(),那也是沒問題的。

但是注意因為我們的值是復制進去的,所以最原始的foo不會被改變了。

最后

以上我們用了三種方法解決了move value的報錯。

實際還有很多種方法可以用於在不同的場景中避免這個錯誤。

有興趣的同學可以繼續玩耍,最后提供一個跟蹤變量生命周期的方法以供愉快玩耍。

實現Drop Trait在變量被釋放或重分配空間時打印日志,代碼如下:

use std::fmt;
use std::rc::Rc; use std::cell::RefCell; struct Info { s: String, } impl Info { fn new(a: &str) -> Info { println!{"new:{}", a}; Info { s: a.to_string(), } } } impl Drop for Info { fn drop(&mut self) { println!("drop:{}", self.s); } } impl fmt::Display for Info { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { write!(f, "Info.s = {}", self.s) } } impl fmt::Debug for Info { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { write!(f, "Info.s = {}", self.s) } } fn main() { let foo = Info::new("abc"); let mut bar = Rc::new(RefCell::new(Info::new("abc"))); println!("1 : {:?}", bar); println!("code exit"); }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM