Rust学习笔记 —— 所有权

111 阅读3分钟

1. 所有权规则

  1. Rust中的每一个值都有一个所有者变量
  2. 值在任一时刻有且只有一个所有者(变量)
  3. 所有者离开作用域的时候,这个值将会被丢弃(drop) 这些规则在之后的举例说明里面都会有所体现,需要牢记

例1

let a = "hello";
{
    let b = "aaa";
    println!("{}",b);
}//执行到此时"aaa"这个值的所有者离开了作用域,"aaa"将会被丢弃

2.1 变量域数据交互的方式(一)——移动

例2.1.1

let mut s1 = String::from("hello");
s1.push_str(",move");
println!("{}", s1);
let s2 = s1;//在运行了这个语句之后,Rust认为s1不再有效
//此时使用s1将不能运行
//println!("{}",s1);//error
println!("{}",s2);

在例2.1代码运行到let s2 = s1的时候,我们拷贝了s1在栈上的数据(指针,长度和容量),但是并没有复制s1在堆上的数据,内存中数据的表现如图 2-1 所示

2-1

在这个过程中,s1存储在堆上的数据并没有被复制。而在完成了let s2 = s1语句的时候,s1将会被无效化,此时避免了s1,s2离开作用域时可能引发的二次释放的错误,因为s1被无效化,它并不会因为离开作用域而触发丢弃值的动作,只有s2在离开作用域的时候会丢弃它所拥有的"hello"的值。我们可以解读为s1被移动到了s2,只有s2是有效的,在移动的过程中,始终满足着Rust的所有权规则。

let s2 = s1之后s1被无效,满足了规则2,值在任一时刻都有且只有一个所有者

2.2 变量域数据交互的方式(二):克隆

在有些情况下,我们不但需要复制变量在栈中的数据,也需要复制其在堆中的数据,也就是深拷贝,这时就需要克隆操作,这时我们可以使用clone的通用方法,克隆在内存中数据的表现如图 2-2 所示

2-2

例2.2.1

let s1 = String::from("hello,clone");
let s2 = s1.clone();
println!("{}",s1);//此时sa仍能正常使用
println!("{}",s2);//仍然满足所有权规则

需要注意的是,如果变量是整型这样的数据,则以下代码是有效的

例2.2.2

let inta = 5;
let intb = inta;
println!("in stack:inta = {}, intb = {}",inta,intb);

上面这段代码没有调用clone函数,但是inta依然有效(没有被移动到intb上),原因是像inta,intb这样已知大小的类型(i32)会被整个存储到栈上,拷贝这个值的过程是非常快速的,可以直接使用拷贝。

在Rust上有一个叫做Copy trait的特殊注解,可以用在类似整型这样存储在栈上的类型上,如果一个类型实现了Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。(类似clone)。如果一个类型自身或者它的任何一部分实现了Drop trait,那么不允许其再实现Copy trait。

实现了Copy的类型

  • 所有的整数类型
  • 所有的浮点数类型
  • 布尔类型
  • 字符类型
  • 仅包含以上四种类型的元组

所有权与函数

将值传递给函数在语义上与给变量赋值类似,method(x)对x的影响类似于y = x

let s = String::from("hello");
takes_ownership(s);
//同复制一样,值的所有权从s交给了takes_ownership函数的some_string形参
//发生了移动,在执行了函数之后,some_string变量离开作用域,丢弃了它拥有的值
// println!("{}",s);//error此时发生了移动,s被弃用

//takes_ownership函数的定义
fn takes_ownership(some_string:String){
    println!("{}",some_string);
}

返回值也可以转移所有权