理解Rust变量的所有权、引用与Copy,Clone的关系
变量的可变与不变
rust中,使用let声明的变量是不可变的。例如:
fn main() {
let i = 100;
//编译失败
i = 200;
}
不同于java中的final,rust变量的不可变包括对象属性值的不可变。例如在Java中:
final Map<String, Integer> map = new HashMap<>();
//虽然map是final,但对象内部值仍然是可变的。
map.put("one", 1);
//编译失败,变量map是final,不能修改。
map = new HashMap<>();
而在rust中:
use std::collections::HashMap;
let map: HashMap<String, i32> = HashMap::new();
//编译失败,变量map是不可变的,内部值也不允许修改。
map.insert("one", 1);
使用let mut声明变量是可变的。
变量所有权与Copy,Clone的关系
rust中,所有的"值"都是有"所有者"的。这里的"值"代表一块内存空间,数据就存放于这块内存空间中。"值"既可以位于栈,也可以位于堆。而变量就是这块内存空间的"所有者",拥有这块内存空间的"所有权"。例如:
//可变变量i 拥有 一块i32类型所需大小的内存空间 的所有权, 这块内存空间存储的值 是 101
let mut i: i32 = 101;
当拥有"所有权"的变量被赋值给其他变量时,"所有权"也就交给了被赋值的变量。只有拥有"所有权"的变量才能对内存空间中存储的值进行读/写操作。
需要强调的是,"值"的类型是否实现了trait Copy 或trait Clone,或都没有实现,直接影响赋值行为。
Copy和Clone是rust中的2个trait,都表示"复制一份"的意思。Copy继承于Clone。不同点可以简单的理解为存储在栈的"值"需要实现Copy,存储在堆的"值"则需要实现Clone。
具体看Copy、Clone和普通类型赋值行为的不同,以及理解清楚它们的所有权关系。
//Copy类型,rust中,基本数据类型都实现了Copy。
//可变变量i 拥有 一块i32类型所需大小的内存空间 的所有权, 这块内存空间存储的值 是 101
let mut i: i32 = 101;
//将 可变变量i 拥有 的值101 Copy一份,存储在 不可变变量a 所拥有的内存空间中。
let a: i32 = i;
println!("{:?}, {:?}", a, i);
//这时内存中,有2个值为101的空间,所有权分别属于可变变量i和不可变变量a。i和a各自独立,互不影响。
//Clone类型,rust中,String类型实现了trait Clone,但没有实现trait Copy
//可变变量i 拥有 一块内存堆的空间 的所有权, 这块内存空间存储的值 是 hello
let mut i: String = String::from("hello");
//将 可变变量i 拥有 的值hello Clone一份,存储在 不可变变量a 所拥有的内存空间中。
let a: String = i.clone();
println!("{:?}, {:?}", a, i);
//这时内存中,有2个值为hello的空间,所有权分别属于可变变量i和不可变变量a。i和a各自独立,互不影响。
可以看出,Copy和Clone都产生了"复制一份"的目的, 区别在于Copy类型是隐式实现的。Clone类型需要调用clone()方法。
如果1个"值"的类型,既不是Copy,也不是Clone,会怎么样呢?
//自定义结构体Foo,既没有实现Copy,也没有实现Clone
#[derive(Debug)]
struct Foo {
age: i32,
}
fn main() {
let mut i: Foo = Foo {age: 20};
//i的值,类型是Foo,Foo不是Copy,也不是Clone,没有clone()方法可以调用,没有发生"复制一份"的行为,这时候就进行内存空间所有权的交接
let a = i;
//编译失败, 可变变量i 的 所有权 交给了 不可变变量a,不再拥有内存空间的所有权。没有所有权就不能进行读/写操作了
println!("{:?}, {:?}", a, i);
}
变量与引用
变量可以将值"借"给其他的变量使用,但不交出所有权。得到这些变量被叫做"引用"。语法上,使用&符号表示"借"的行为。
"引用"同样受可变性的控制。
let i: String = String::from("hello");
//将 不可变变量i 的值,借给 不可变变量a 使用
let a: &String = &i;
//将 不可变变量i 的值,借给 不可变变量b 使用
let b: &String = &i;
println!("{:?}, {:?}, {:?}", a, b, i);
//可变引用,可以修改值,但拥有所有权的变量也必须是可变的。
let mut i: String = String::from("hello");
let a: &mut String = &mut i;
a.push_str(" world");
println!("{:?}", a);
println!("{:?}", i);
从代码中可以看出,声明变量a时,数据类型是&String,说明引用本身就是一种数据类型。
通过引用,也可以操作"值"。
引用的限制:
可变引用与不可变引用不能同时存在。
在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
引用必须总是有效的。
解引用与Copy,Clone的关系
解引用是一种行为。
引用本身是一种数据类型,解引用就是通过引用类型获取真实的"值”。比如对1个&String类型的变量解引用,得到String类型的值。对&i32类型的变量解引用,得到i32类型的值。
解引用也受到"值"类型是否实现了Copy/Clone的影响。
语法上,使用*符号进行解引用操作
//实现了Copy的情况
let i: i32 = 101;
let a: &i32 = &i;
//解引用操纵, 发生了数据类型转换,从&i32转换成了i32。发生了"复制一份"的动作。a和b各自独立,互不影响
let b: i32 = *a;
println!("{}, {}", a, b);
//实现了Clone的情况
let i: String = String::from("hello");
let a: &String = &i;
//解引用操纵, 发生了数据类型转换,从&String转换成了String。发生了"复制一份"的动作。a和b各自独立,互不影响
let b: String = a.clone();
println!("{}, {}", a, b);
//自定义结构体Foo,既没有实现Copy,也没有实现Clone, 无法解引用。
#[derive(Debug)]
struct Foo {
age: i32,
}
fn main() {
let i: Foo = Foo {age: 20};
let a: &Foo = &i;
//编译失败,Foo类型没有实现Copy, 也无法调用clone()方法。
let b: Foo = *a;
println!("{:?}, {:?}", a, b);
}
由于引用没有所有权,解引用实际还是执行的"复制一份"的逻辑。如果不能完成"复制一份"的行为,则编译失败。
通过解引用的语法赋值
#[derive(Debug)]
struct Foo {
age: i32,
}
fn main() {
let mut i: Foo = Foo {age: 20};
let a: &mut Foo = &mut i;
//将一个新值赋值给a, 由于a可以使用”值", 所以也会导致拥有所有权的i也被赋予相同的值。
*a = Foo {age: 30};
println!("{:?}", a);
println!("{:?}", i);
}
引用就是指针, 对引用的操作会传导给拥有所有权的变量。
后续再补充所有权/引用的特殊情况。例如闭包,共享所有权等操作。