Rust:所有权

什么是所有权

所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用地内存;在另一些语言中,程序员必须亲自分配和释放内存。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》

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值