什么是所有权
所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用地内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust则选择了第3种方式:通过所有权系统管理内存,编译器在编译时会根据一系列地规则进行检查。在运行时,所有权系统地任何功能都不会减慢程序
所有权系统要处理的问题:
- 跟踪哪部分代码正在使用堆上的哪些数据
- 最大限度的减少堆上重复数据的数量
- 清理堆上不再使用的数据确保不会耗尽空间。
所有权的存在就是为了管理堆数据。一旦理解了所有权,就不需要经常考虑栈和堆了
所有权规则:
- 每个值在Rust中都有一个变量来管理它,这个变量就是这个值、这块内存的所有者
- 每个值在一个时间点上只有一个管理者
- 当变量所在作用域结束的时候,变量以及它所代表的值都将会被销毁
所有权转移:移动语义
move语义
一个变量可以把它拥有的值转移给另外一个变量,称为“所有权转移”。
fn main(){
let s = String::from("hello"); //s获取了"hello"
let s1 = s; //原本由s拥有的字符串已经转移给了s1这个变量
println!("{}", s); //error[E0382]: borrow of moved value: `s`
}
每个值只有一个所有者。变量s的生命周期从声明开始,到move给s1就结束了。变量s1的生命周期则是从它声明开始,到函数结束。而字符串本身,由String::from函数创建出来,到函数结束的时候就会销毁。中间所有权的转换,并不会将这个字符串本身重新销毁再创建。在任意时刻,这个字符串只有一个所有者,要么是s,要么是s1
fn create() -> String {
let s = String::from("hello");
return s; // 所有权转移,从函数内部移动到外部
}
fn consume(s: String) { // 所有权转移,从函数外部移动到内部
println!("{}", s);
}
fn main() {
let s = create();
consume(s);
}
Rust中所有权转移的重要特点是,它是所有类型的默认语义:Rust中的变量绑定,默认是move语义,当把值绑定给另外一个变量之后,原来的变量就不能再使用。
所有权不变:复制语义
clone
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); /重新在堆上分配新的内存[这个内存和s1指向的堆一模一样,除了地址不相同以外]
println!("s1 = {}, s2 = {}", s1, s2);
}
默认情况下是移动语义,如果想要实现复制,对于复杂数据类型,必须手动clone。
copy
但是在Rust中,凡是实现了std::marker::Copy trait的类型,都会执行copy语义,比如标量类型、函数传参
fn main() {
let x = 5; //向OS申请一个栈空间x,并将5绑定到x,OS会自动将这个空间标记为已使用
let y = x; //向OS申请一个栈空间y,将x变量绑定的值5拷贝到一个临时变量中,绑定到y,然后使得临时变量失效
println!("{},{}",x, y);
}
因为正数是已经大小固定的,所以x,y,临时变量都在栈中操作
Clone VS Copy
什么是copy语义
在std::marker模块中里面所有的trait都是特殊的trait,它们的唯一任务是给类型打一个“标记”,表名它符号某种约定。这些约定会影响编译器的静态检测以及代码生成。
如果一个类型impl了Copy trait,当进行变量绑定,函数传参,函数返回值等时,原来的变量不会失效,而是新开辟一块内存,将原来的数据复制过来
一旦一个类型实现了copy trait,那么它在等场景下,都是copy语义
哪些类型可以实现copy
- 常见的标量类型等具有Copy属性
- 对于数组/元组,只要内部的元素类型是copy,那么这个数组/元组也是copy
- 对于struct/enum类型,就算内部成员是copy类型,但是它本身并不是copy类型
struct T(i32);
fn main() {
let t1 = T(1);
let t2 = t1;
println!("{}", t1.0) //error[E0382]: borrow of moved value: `t1`
}
但是我们可以针对这样的struct/enum实现Copy:因为它们内部的每个元素都是copy类型的
struct T(i32);
impl Copy for T{}
fn main() {
let t1 = T(1);
let t2 = t1;
println!("{}", t1.0) //error[E0382]: borrow of moved value: `t1`
}
但是错误:error[E0277]: the trait bound T: std::clone::Clone
is not satisfied。错误是因为Copy继承了Clone,我们要实现Copy trait就要同时实现Clone trait
struct T(i32);
impl Clone for T{
fn clone(&self)->T{
T(self.0)
}
}
impl Copy for T{}
fn main() {
let t1 = T(1);
let t2 = t1;
println!("{}", t1.0)
}
当有成员不是Copy类型的,就不能实现Copy
- 自定义类型:同struct和enum。【其实struct和enum就是官方支持的自定义类型】
struct T {
data:i32,
}
impl Clone for T{
fn clone(&self)->T{
T{data:self.data}
}
}
impl Copy for T{}
fn main() {
let t1 = T{data:1};
let t2 = t1;
println!("{}", t1.data)
}
编译器提供了一个编译器扩展derive attribute,来帮助我们对一个类型实现Clone trait和Copy trait.推荐
#[derive(Copy, Clone)]
struct T(i32);
fn main() {
let t1 = T(1);
let t2 = t1;
println!("{}", t1.0)
}
clone语义⭐⭐⭐【不太懂】
Clone的全名是std::clone::Clone。它的完整声明如下:
pub trait Clone : Sized{
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &Self) {
*self = source.clone()
}
它有两个关联方法,分别是clone_from和clone,clone_from是有默认实现的,依赖于clone方法的实现。clone方法没有默认实现,需要手动实现
copy语义与move语义的区别:
copy相当于复制黏贴,move相当于剪切黏贴,它们都实现了简单的内存值赋值,区别在于复制完成之后,原来那个变量的声明周期是否结束。
总结
- Copy内部没有方法,Clone内部有两个方法
- Copy trait是给编译器用的,告诉编译器这个类型默认采用Copy语义,而不是move语义。Clone trait是给程序员用的,我们必须手动调用clone方法,它才能发挥作用
- 并不是所有类型都可以实现Copy trait,而Clone trait任何类型都可以实现[除了unsized,因为无法使用unsized类型作为返回值]
- Copy trait规定了这个类型在执行变量绑定、函数参数传递、函数返回等场景下的操作方式:“简单内存复制”,这是由编译器保证的,程序员无法控制。Clone trait里面的clone方法实现什么操作,取决于程序员自己。一般来讲,clone方法执行“深复制”操作,但是这不是强制性的
- 如果不想clone trait执行其他操作,编译器提供了一个工具,我们可以在一个类型上添加#[derive(clone)],来让编译器帮我们自动生成重复代码。编译器依次调用每个成员的clone方法来实现这个类型的clone
参考:https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html
参考:《深入浅出Rust》