这部分是Rust语言的核心部分,掌握起来有一定难度,特别是生命周期部分,让人有Rust的学习曲线陡升的感觉,爬过这座高峰,其它皆坦途。
这部分也是让人觉得Rust语言比其它语言如C/C++等复杂的主要原因之一,即使是写文章介绍起来也感觉不容易。
**一、所有权(ownership)
**
基本概念:一个变量同一个时刻只能有一个拥有者。
所有权概念使Rust确保了对于任何给定的资源都正好(只)有一个绑定与之对应。
如同一个房子只能有一个主人,let的操作过程如同政府给这个主人颁发了房产证。
- 所有权是变量绑定的一个属性,发生来let的过程中。
let v = vec![1, 2, 3];
let v2 = v;
第一行为向量(vector)对象和它包含的数据分配了内存。向量对象储存在栈上并包含一个指向堆上 [1,2, 3] 内容的指针。
第二行当我们从 v 移动到 v2 ,它为 v2 创建了一个那个指针的拷贝。这意味着这将会有两个 所有权指向向量内容的指针。这将会因为引入了一个数据竞争而违反Rust的安全保证。因此,Rust禁止我们在移 动后使用 v 。
总结:了当所有权被转移给另一个绑定以后,你不能再使用原始绑定。
2.一些特殊情况不会出现这种所有权限制:
情况1:trait,它会实现copy。(trait类似接口定义的关键字,如java的interface)
情况2:所有基本类型会自动实现copy。
let v = 1;
let v2 = v;
println!("v is: {}", v);
在这个情况, v 是一个 i32 ,它实现了 Copy 。这意味着,就像一个移动,当我们把 v 赋值给 v2 ,产生了 一个数据的拷贝。不过,不像一个移动,我们仍可以在之后使用 v 。这是因为 i32 并没有指向其它数据的 指针,对它的拷贝是一个完整的拷贝。
二、借用和引用(borowwing and Reference)
引用类似于C语言的指针,连符号都是一样的:&, &point,&a
首先定义 let a = vec![1, 2, 3] 假设首先定义一座可以住3个人的大房子,房主是a。
&a 意味着引用a的资源,就像出租屋管理中心登记了房子a的门牌号,以后找这座房子a就方便了
那么let b = &a, 意味a将房子出租给b,a虽然仍是房子的主人,但是由于将房子出租给了b,在出租期间,房主人a是不能使用的。
正式的说法就是,b借用了a的资源,至于借出的期限是多少,这个涉及下个节的生命周期,在这个期限内,a不能使用,只有过了这个期限,才可以重新使用。
如果是let b = &mut a, 这个是最大权限的借出了,借出期间,b可以随便修改房子里的东西。
第一种类型的引用:&T,我们称 &T 类型为一个”引用“,而与其拥有这个资源,它借用了所有权。一个借用变量的 绑定在它离开作用域时并不释放资源。这意味着 foo() 调用之后,我们可以再次使用原始的绑定。
引用是不可变的,就像绑定一样。这意味着在中,向量完全不能被改变:
fn foo(v: &Vec<i32>) {
v.push(5);
}
let v = vec![];
foo(&v);
上述代码是会报错的
第二种类型的引用:&mut T,称为“可变引用”,即允许你改变你借用的资源。
首先,被引用变量必须也是mut类型。
let mut x = 5;
let y = &mut x; //x资源给y借用了
*y += 1;
println!("{}", x);// 这里尝试借用x
// &mut x 的借用到这里才结束
=============================以上错误,这些作用域冲突了:我们不能在 y 在作用域中时生成一个 &x 。
let mut x = 5;
{
let y = &mut x;
*y += 1;
}
println!("{}", x);
=============================以上正确,用{}限定了范围
这会打印 6 。我们让 y 是一个 x 的可变引用,接着把 y 指向的值加一。你会注意到 x 也必须被标记为 mut ,如果它不是,我们不能获取一个不可变值的可变引用。
我们的可变借用在我们创建一个不可变引用之前离开了作用域。
借用(borowwing)有一些规则
第一,任何借用必须位于比拥有者更小的作用域。(真正主人的使用期限当然要大于借用者的使用期限)
第二,你可以有一个或另一个这两种类型的借用,不过不能同时拥有它们。(东西不能同时借给很多人用,轮流用才行)
0个或N个资源的引用(&T)。
只有1个可变引用((&mut T)。
通过引用,你可以拥有你像拥有的任意多的引用,因为它们没有一个在写。如果你在写,并且你需要2个或 更多相同内存的指针,则你只能一次拥有一个 &mut 。这就是Rust如何在编译时避免数据竞争:我们会得到 错误,如果我们打破规则的话。
**三、生命周期(困难曲线陡升的东西)
**
-
http://stackoverflow.com/questions/17490716/lifetimes-in-rust
-
案例:https://github.com/hyperium/hyper/blob/master/src/server/request.rs
1、生命周期是什么?
https://github.com/rustcc/RustPrimer/blob/master/13-ownership-system/13-03-lifetimes.md
理论上讲,生命周期是一种构想,在这个构想里面,编译器将用于确保所有变量的借用是有效的。一个变量的生命周期开始于这个变量被创造的时候,结束于这个变量被摧毁的时候。另外,生命周期lifetime和scope经常给一起提及,但是它们是不一样的。一般来讲,可以通过Scope作用域来理解生命周期,“生命周期”在编译阶段就已经决定变量应该在什么时候销毁内存该什么时候释放,生命周期就是告诉编译器,内存什么时候销毁。
通俗的讲,生命周期就是a借东西给b,借用期限就是这个生命周期了,每个借用都必须有借用期,不能随便借出不还了,想永久霸占是不行滴,当然有时候在一定条件下,这东西如果借给你复制拷贝一下,也是可以的。lifetime和scope可以理解成使用期限和使用范围。
总之,要理解生命周期,必须先要完全理解上面的所有权,引用和借用的概念。
2、生命周期用什么表达?
使用单引号加单个字母:'a
有一个生命周期:foo<'a> 表示foo有个生命周期'a
说明:使用生命周期,需要用到泛型<>,这种表达意味着foo的生命周期不可能超过它的泛型'a
类型的精确的注解还有 & 'a T 这种形式,
有多个生命周期:foo<'a, 'b>
说明:这种表达以为foo的生命周期不可能超过它的泛型'a 或'b
3、什么场合要用到生命周期
**函数
**
- any reference must have an annotated lifetime.
- any reference being returned must have the same lifetime as an input or be static.
A.对函数来说,任何引用都必须有一个被注解的生命周期。
B.任何引用被函数返回时,都必须有同样的生命周期被输入或者是静态的参数。
只有函数返回引用时,必须加lifetime,如果不加,编译器不知道返回值的生存期。
实例1,fun1因只有一个入参,返回的lifetime默认跟入参一致,但fun2就必须要加lifetime了
fn main() {
let mut i = 10;
let k = fun1(&mut i);
println!("{:?}", k);
let mut x = 10;
let mut y = 5;
let z = fun2(&mut x, &y);
println!("{:?}", z);
}
fn fun1(c: &mut i32) -> &i32 {
*c = *c + 1; c
}
fn fun2<'a,'b>(c1: &'a mut i32, c2: &'b i32) -> &'a i32 {
*c1 = *c1 + *c2;
c1
}
每一个借用都会有一个生存期。生存期用来表示借用的有效范围。
Rust对于每一个引用的生命周期都是严格控制的,在编译期就保证了引用的阳寿不会长过它引用的对象,从而确保了“野指针”的 不可发生性 。
**结构体
**
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let y = &5; // this is the same as
1 | 1` |
let _y = 5; let y = &_y;
1 | 1` |
let f = Foo { x: y };
println!("{}", f.x);
}
使用它,是因为我们需要确保任何 Foo 的引用不能比它包含 的 i32 的引用活的更久。
在整个impl块中:
struct Foo<'a> {
x: &'a i32,
}
impl<'a> Foo<'a> {
fn x(&self) -> &'a i32 { self.x }
}
fn main() {
let y = &5; // this is the same as
1 | 1` |
let _y = 5; let y = &_y;
1 | 1` |
let f = Foo { x: y };
println!("x is: {}", f.x());
}
如你所见,我们需要在 impl 行为 Foo 声明一个生命周期。我们重复了 'a 两次,就像在函数 中:
impl<'a> 定义了一个生命周期 'a ,而 Foo<'a> 使用它。
4、Lifetime要解决什么问题?
其中一个就是要解决悬垂指针“Dangling pointer”的问题,比如
fn dangling<'a>() -> &'a i32 {
letlocal_var: i32 = 1;
&local_var
}
**5、关于Lifetime到底是怎么推导的?
**
生存期是用来做类型限定的,并不是很多人口中说的用来做推断的。
它就是类型系统的一部分,不是有什么用,是必须用,否则就必须放弃今天的以item为范围进行推导而改成在全局推导。
不妨看一下这段程序:
fn echo<'a>(a: &'a str) -> &'a str {
a
}
如上这个函数echo,它接受一个参数a,然后直接把它返回回去,返回值的Lifetime与参数一致。
那么如果有如下调用:
let ret = echo("hello world");
那么我们就开始推导,首先"hello world"的类型是&'static str,代入到echo中,'a = 'static,因此返回值ret的类型就是&'static str。验证结论的方法是
let ret: &'static str = echo("hello world");
并不会报错。
那么如果我们传别的呢?
fn outer() {
// … 其它代码
let string: String = "hello world".to_owned();
let ret = echo(&string[..]);
// … 其它代码
}
在这种情况下'a应该是什么呢?答案就是string的作用域(Scope),即outer函数体。