Rust含有所有权系统来管理内存.在编译时,所有权系统检查一组规则来确保所有权特性允许你程序运行不会变慢.
为明白所有权让我们先看下Rust作用域规则和移动语义.
在Rust中像其它语言,变量可用公在确定的作用域. 在Rust中,作用域通常表示通过{ }
.一般作用域包含函数体和if
, else
match
分支.
注意:在Rust中,“变量”通常被称为“绑定”.这里因为在Rust中“变量”非常不变量 - 他们通常不会改变因为他们默认不变.代替,我们通常认为名称与数据“绑定”,因此命名为“绑定”.不过我们使用“变量”和“绑定”两个交替使用.
我们有一个mascot
变量,它是一个string,定义在作用域:
// `mascot` is not valid and cannot be used here, because it's not yet declared. { let mascot = String::from("ferris"); // `mascot` is valid from this point forward. // do stuff with `mascot`. } // this scope is now over, so `mascot` is no longer valid and cannot be used.
如果我们试着在它的作用域外使用mascot
,会得到下例一样的错误
{ let mascot = String::from("ferris"); } println!("{}", mascot);
error[E0425]: cannot find value `mascot` in this scope --> src/main.rs:5:20 | 5 | println!("{}", mascot); | ^^^^^^ not found in this scope
变量是可用的从它声明点开始,直到作用域末结束.
Rust增加一个不一样想法的作用域.无论什么时候,对象超出作用域,它会“被丢掉”.Dropping一个变量意味着释放任何资源,被绑定在他上的.对于文件变量,文件最后会关闭.对于变量分配内存和他们,这个内存会释放.
在Rust中绑定,他有东西“关联”和他们,他们会释放当绑定被丢掉,是说“拥有”那些东西.
在上边例子, mascot 变量拥有String 关联它. String它自己拥有堆分配内存,拥有string特质.在作用域末尾, mascot 被“丢掉”,String 它自己是被丢掉的,并且最后内存中String自己被释放.
{ let mascot = String::from("ferris"); // mascot dropped here. The string data memory will be freed here. }
有时通过我们不想要的东西关联一个变量来丢掉在作用域末尾.相反,我们想要转换一个对象所有权从一个绑定到另外一个.
当声明新绑定的最简单例子:
{ let mascot = String::from("ferris"); // transfer ownership of mascot to the variable ferris. let ferris = mascot; // ferris dropped here. The string data memory will be freed here. }
关键是要明白一旦所有权被转移,老的变量不在可用.在我们上边例子,我们转移String所有权从mascot 到 ferris 后,我们不能再使用mascot变量.
在Rust中,“转移所有权”就是被熟知的“移动”.换言之,在上边例子,String 值已经被移动从 mascot 到 ferris.
如果我们使用mascot 在String 已经移动从mascot 到 ferris后, 编译器不能编译代码:
{ let mascot = String::from("ferris"); let ferris = mascot; println!("{}", mascot) // We'll try to use mascot after we've moved ownership of the string data from mascot to ferris. }
error[E0382]: borrow of moved value: `mascot` --> src/main.rs:4:20 | 2 | let mascot = String::from("ferris"); | ------ move occurs because `mascot` has type `String`, which does not implement the `Copy` trait 3 | let ferris = mascot; | ------ value moved here 4 | println!("{}", mascot); | ^^^^^^ value borrowed here after move
这就是有名的“use after move” 编译错误.
重要: 在Rust中,只有一个可以一直拥有这段时间的数据.
让我们看一个例子关于字符串转入到函数作为参数. 传一些东西作为参数给函数,移动它们到函数.
fn process(input: String) {} fn caller() { let s = String::from("Hello, world!"); process(s); // Ownership of the string in `s` moved into `process` process(s); // Error! ownership already moved. }
编译器抱怨值s已经移动
error[E0382]: use of moved value: `s` --> src/main.rs:6:13 | 4 | let s = String::from("Hello, world!"); | - move occurs because `s` has type `String`, which does not implement the `Copy` trait 5 | process(s); // Transfers ownership of `s` to `process` | - value moved here 6 | process(s); // Error! ownership already transferred. | ^ value used here after move
正如你在上边代码看到,首先调用process
传变弟变量s所有权. 编译器跟踪所有权,所以下次调用process 出现错误.资源被移动后,上一个所有者不在可以使用了.
这样的模式极大影响Rust编码方式.它核心是保证内存安全,Rust提议的.
在其它语言,s变量的String值 可以隐式拷贝在传入我们函数之前.但是Rust中这个操作不会发生.
在Rust中,所有权转换(那个是,移动)是默认行为.
你可能注意到在上边的(相当于提供信息)编译器错误消息,Copy
trait被提到.我们目前没有讨论过trait,但是值实现Copy
trait,没有得到移动而是复制.
让我们看下值实现了Copy
trait u32
. 下边代码反映了我们违反上边的代码,但是编译器没有出现问题.
fn process(input: u32) {} fn caller() { let n = 1u32; process(n); // Ownership of the number in `n` copied into `process` process(n); // `n` can be used again because it wasn't moved, it was copied. }
简单的类型像数字 copy类型.他们实现了Copy
trait, 这意味着他们复制相当于移动.相同动作发生对于简单类型.复制数值是低消耗的,所以它是有意义的对于那些值被复制的.复制string 或 vector 或其它复杂类型非常代价大,所以他们没有实现Copy
trait并且用移动代替.
Copy
一种方式来处理错误,我们看到上边显示通过在他移动之前复制类型: 用Rust中cloning. A 调用.clone会复制内存并生成新值.新的值被移动意味着老的值仍可以使用.
fn process(s: String) {} fn main() { let s = String::from("Hello, world!"); process(s.clone()); // Passing another value, cloned from `s`. process(s); // s was never moved and so it can still be used. }
这种方法可以被用,但是它可以使你的代码变慢,当每个调用用clone
是一个完整拷贝数据.这个方法通常包含内存分配或其它代价大的操作.我们可以避免这些开销如果我们可以“借”值通过引用.我们学习引用在下个单元.
https://docs.microsoft.com/en-us/learn/modules/rust-memory-management/2-learn-about-borrowing