Rust 所有权及借用规则
- 单一所有权
- Rust 明确了所有权的概念,所有权就是拥有资源的管理权利
- 在 Rust 中,值也是一种资源,每一个值都有一个被称为其所有者(owner)的变量;值在任何时候都只能有一个所有者;当所有者(变量)离开作用域,这个值将被丢弃(drop)
- Rust 通过单一所有权来限制任意引用的行为
- 赋值和参数传递通常是编程语言不可或缺的组成部分,Rust 对其定义有三种语义行为
- 移动语义(move):即所有权转移,将值的所有权转移给新的所有者
- 复制语义(copy)
- 借用语义(borrow)
- 在 Rust 中,“借用”本质上就是“引用”,只不过多了些规则,便提出了新概念“借用”
- 创建一个引用的行为称为借用
&T:不可变借用&mut T: 可变借用
- 借用规则
- 可存在多个不可变借用,但只能有一个可变借用
- 可变借用与不可变借用的作用域不能交叠,可变借用之间的作用域也不能交叠
- 借用只在所有权者的生命周期内才有效,防止悬空引用
- 所有权者不能在借用期内修改值或者转移
- 生命周期注释
Clone Trait vs Copy Trait
- Clone trait 是 Copy trait 的 supertait(基类)
- 无论是 Clone 还是 Copy,目的都是为了拷贝值
- Copy 是隐式复制行为,由编译器自动执行的;它是一个标记特征,告诉编译器表明该类型可以安全、简单得直接按位拷贝
- 按位拷贝:等同于 C 语言里的 memcpy:C 语言中的 memcpy 会从源所指的内存地址的起始位置开始拷贝 n 个字节,直到目标所指的内存地址的结束位置。但如果要拷贝的数据中包含指针,该函数并不会连同指针指向的数据一起拷贝
- 如何实现 Copy 特征
struct MyStruct; impl Copy for MyStruct { } impl Clone for MyStruct { fn clone(&self) -> MyStruct { *self // 直接解引用到原值 } } - 在 Rust 中,什么数据结构实现了 Copy trait 呢?
- Rust 标准库中实现 Copy trait 的所有数据结构
- 大概心智模型
- 固定大小的数据类型,如标量类型、共享只读引用、函数指针等
- 无法安全复制的类型不能实现 Copy trait
- 可变引用类型
- 任何类型的实现 Drop trait,防止双重释放
- 需要所有成员都是 Copy trait 的,结构体才能实现 Copy trait
- Clone 则是开发者通过 clone 方法显示调用的
- Copy 无法重载,而 Clone trait 则可以重载
- Copy trait 和 Clone trait 本质上都是按位复制,但 Clone trait 可能会涉及到更多的内存分配和操作,而 Copy trait 则只是简单地复制值,Clone trait 更多期望实现真正克隆语义。故在 Rust 中,Copy 的特征充当于“浅”克隆,而 Clone 则相当于“深”拷贝
- Rust 标准库中实现 Clone trait 的所有数据结构
- Copy 是隐式复制行为,由编译器自动执行的;它是一个标记特征,告诉编译器表明该类型可以安全、简单得直接按位拷贝
生命周期
- 每个引用都有自己的生命周期,一旦超过其生命周期,其拥有的值被丢弃
// 当程序离开 "hello" 所有者变量 s 所在生命周期(作用域),其拥有的值被丢弃。 fn main() { let s_ref; —————————————————————— { | let s = String::from("hello"); ---------- | | | s 生命周期范围 s_ref 生命周期范围 s_ref = &s; _________| | } | println!("{}", s_ref); —————————————————————— } - Rust 编译器有一个借用检查器,通过比较生命周期来确保所有的借用都是有效的,确保了引用的有效性,避免了悬垂引用
- 在单个函数上文内,Rust 可以通过作用域推导借用的生命周期,但不同函数上下文之间的返回借用则需要泛型生命周期标注
- Rust 无法推导动态逻辑下返回的借用的生命周期
fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } } - 返回的借用需要标注与入参的生命周期关联
// 表示返回的引用和这两个参数至少存活的一样久 fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } } - 如果返回的引用没有指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值。然而它将会是一个悬垂引用,因为它将会在函数结束时离开作用域
fn longest<'a>(x: &str, y: &str) -> &'a String { let s = String::from("hello"); &s // error }
- Rust 无法推导动态逻辑下返回的借用的生命周期
- 结构体定义中的生命周期注解
- 在 Rust 中结构体定义中的每一个引用需要添加生命周期注解
struct ImportantExcerpt<'a> { part: &'a str, } - 方法定义中的生命周期注解
impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &'a str { println!("Attention please: {}", announcement); self.part } }
- 在 Rust 中结构体定义中的每一个引用需要添加生命周期注解
- 静态生命周期
'static- 所有的字符串字面值都拥有
'static生命周期let s: &'static str = "I have a static lifetime.";
- 所有的字符串字面值都拥有
- 生命周期省略规则(如果代码符合这些场景,就无需明确指定生命周期)
- 第一条规则是编译器为每一个引用参数都分配一个生命周期参数
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
- 第二条规则是如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
fn foo<'a>(x: &'a i32) -> &'a i32
- 结构体中的方法的所有返回的借用默认 self 的生命周期
- 第一条规则是编译器为每一个引用参数都分配一个生命周期参数
其他文章
交流区
加载中...