一,前言
三个概念,一直在阶段性的困扰我,感觉还是尝试自己说一下,再加点案例,能够把这些讲清楚后,会有一个清晰的认识吧。
二,所有权
Rust 注重安全和速度。它通过很多零开销抽象(zero-cost abstractions)来实现这些目标,也就是说在 Rust 中,实现抽象的开销尽可能的小。所有权系统是一个典型的零开销抽象的例子。
所有权规则
- Rust中的每个值都有一个变量,称为其所有者。
- 一次只能有一个所有者。
- 当所有者超出范围时,该值将被删除。
只有活着时候存在,像是个人财产,例如手里的黄金,默认只属于你个人(只读不可变状态)。黄金转让后消失(移动后失去所有权),且在非特定情况下(可变借用)不会改变,在意外(声明周期结束)发生后彻底消失。
下面案例说明上述观点:
let v = vec![1, 2, 3];
let v2 = v;
// println!("v[0] is: {}", v[0]);//value borrowed here after move
特殊案例:Copy
类型
当所有权被转移给另一个绑定以后,你不能再使用原始绑定,trait会改变这个行为,它叫做Copy。
所有基本类型都实现了Copy trait,因此他们的所有权并不像你想象的那样遵循“所有权规则”被移动,当然针对非基本类型也可以编写相对类型的Copy。
可以理解为一个为特定类型增加额外行为的标记,但必须是同一个作用域,并且不能修改。
当我们把v赋值给v2,产生了一个数据的拷贝。不过,不像一个移动,我们仍可以在之后使用v。这是因为i32并没有指向其它数据的指针,对它的拷贝是一个完整的拷贝。
let v = 1;
let v2 = v;
println!("v is: {} {} ",v,v2);
console:
v is: 1 1
Copy
类型有哪些呢?
基础类型和基础类型的元组。基础类型:所有整数,所有浮点,布尔,字符char。
三,借用和可变借用
对于非基本类型呢? 如果想继续使用,还得在还回去,会使使用变得复杂。相当于初始状态引用卡借了钱,手动还回去。
let v = vec![1, 2, 3];
let v2 = v;
println!("v2[0] is: {}", v2[0]);
let v = v2;
println!("v[0] is: {}", v[0]);
console:
v2[0] is: 1
v[0] is: 1
(一)借用
相当于rust自动归还所有权,此时引用卡设置了自动还款,无需额外操作。
let v = vec![1, 2, 3];
let v2 = &v;
println!("v2[0] is: {}", v2[0]);
println!("v[0] is: {}", v[0]);//不能在同一个宏内输出,不能修改
console:
v2[0] is: 1
v[0] is: 1
(二)可变借用
允许你改变你借用的资源,此时就比较厉害了,在自动还款的前提下,借了钱可以多还也可以少还,正负利率。
let mut v = vec![1, 2, 3];
println!("v[0] is: {}", v[0]);
let v2 = &mut v;
v2[0] += 1;
println!("v[0] is: {}", v[0]);
console:
v[0] is: 1
v[0] is: 2
(三)小结
Rust 中的借用有一些规则:
第一,任何借用必须位于比拥有者更小的作用域。
第二,同一个作用域下,不能同时存在可变和不可变的引用:
0 个或 N 个资源的引用(&T)
只有 1 个可变引用(&mut T)
当 2 个或更多个指针同时访问同一内存位置,当它们中至少有 1 个在写,同时操作并不是同步的时候存在一个“数据竞争”
相当于读可以共享,写不能
四,生命周期
'a
读作“生命周期 a”,通常情况下被省略。 生命周期的主要目标是避免悬垂引用,它会导致程序引用了非预期引用的数据。
我们知道当v2 = v1 ,发生所有权转移后,不用手动和GC清理内存,编译器处理:当所有权消失时清理,避免了野指针(未经初始化的“垃圾值”地址)。rust的生命周期就是定义了一个引用有效的作用域,从而避免无效引用(指向已经释放的地址)。
举个栗子:
1,我1号发工资10元;
2,我答应3号借给你5元;
3,我2号买东西花完了;
4,你3号决定使用这个钱;
在rust中体现,在let b = a;时,a已经没钱了。在let c = a; 时,rust就会提示你语法错误
fn main() {
let a = "10".to_string(); // -+ a goes into scope
let b = a; // -+ a goes out of scope
let c = a; //value used here after move
println!("{}", c)
}
作用域越大存在的越久,被引用的对象不能比它的引用者存在的时间更短。
实现返回两个字面变量中较长的一个的方法
fn main() {
let result = longest("abcd", "xyz");
println!("The longest string is {}", result);
}
fn longest>(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
} else {
y
}
}
抛出以下错误
help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
提示我们添加生命周期注解,这是因为
借用检查器无法知道返回的引用是指向 x 或 y
rust有时并不能确认声明周期时,需要注解来帮助他确定生命周期,确保结束前一致。
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
生命周期注解语法
生命周期参数名称必须以撇号(')开头,其名称通常全是小写,默认 'a 。用于 & 之后,并有一个空格与引用类型分开(例如 y: &'a str)。
单个生命周期注解没有意义,因为生命周期注解描述了多个引用生命周期相互的关系。但并不影响其生命周期的长短,而是指出任何不遵守这个协议的传入值都将被借用检查器拒绝。
函数定义指定了签名中所有的引用必须有相同的生命周期,与当函数签名中指定了泛型类型参数后就可以接受任何类型一样。
泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个。所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效。