Rust语言开发基础(七)Rust 特性

这部分是Rust语言的核心部分,掌握起来有必定难度,特别是生命周期部分,让人有Rust的学习曲线陡升的感受,爬过这座高峰,其它皆坦途。
这部分也是让人以为Rust语言比其它语言如C/C++等复杂的主要缘由之一,即便是写文章介绍起来也感受不容易。
java

1、全部权(ownership)
基本概念:一个变量同一个时刻只能有一个拥有者。
全部权概念使Rust确保了对于任何给定的资源都正好(只)有一个绑定与之对应。
如同一个房子只能有一个主人,let的操做过程如同政府给这个主人颁发了房产证。
git

1. 全部权是变量绑定的一个属性,发生来let的过程当中。github

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 并无指向其它数据的 指针,对它的拷贝是一个完整的拷贝。this

2、借用和引用(borowwing and Reference)spa

引用相似于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);
上述代码是会报错的
server

第二种类型的引用:&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如何在编译时避免数据竞争:咱们会获得 错误,若是咱们打破规则的话。

3、生命周期(困难曲线陡升的东西)
1. http://www.rust.cc/t/rustde-lifetimede-zuo-yong/712/6
2. https://wiki.rust-china.org/Rust-Lifetime
3. http://stackoverflow.com/questions/17490716/lifetimes-in-rust
4. https://wiki.rust-china.org/Rust-Lifetime
5. 案例:https://github.com/hyperium/hyper/blob/master/src/server/request.rs

一、生命周期是什么?
https://github.com/rustcc/RustPrimer/blob/master/13-ownership-system/13-03-lifetimes.md
理论上讲,生命周期是一种构想,在这个构想里面,编译器将用于确保全部变量的借用是有效的。一个变量的生命周期开始于这个变量被创造的时候,结束于这个变量被摧毁的时候。另外,生命周期lifetime和scope常常给一块儿说起,可是它们是不同的。通常来说,能够经过Scope做用域来理解生命周期,“生命周期”在编译阶段就已经决定变量应该在何时销毁内存该何时释放,生命周期就是告诉编译器,内存何时销毁。

通俗的讲,生命周期就是a借东西给b,借用期限就是这个生命周期了,每一个借用都必须有借用期,不能随便借出不还了,想永久霸占是不行滴,固然有时候在必定条件下,这东西若是借给你复制拷贝一下,也是能够的。lifetime和scope能够理解成使用期限和使用范围。

总之,要理解生命周期,必须先要彻底理解上面的全部权,引用和借用的概念。

二、生命周期用什么表达?
使用单引号加单个字母:'a

有一个生命周期:foo<'a> 表示foo有个生命周期'a
说明:使用生命周期,须要用到泛型<>,这种表达意味着foo的生命周期不可能超过它的泛型'a
类型的精确的注解还有 & 'a T 这种形式,
有多个生命周期:foo<'a, 'b>
说明:这种表达觉得foo的生命周期不可能超过它的泛型'a 或'b


三、什么场合要用到生命周期
函数
- 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 `let _y = 5; let y = &_y;`
    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 `let _y = 5; let y = &_y;`
    let f = Foo { x: y };
    println!("x is: {}", f.x());
}
如你所见,咱们须要在  impl 行为  Foo 声明一个生命周期。咱们重复了  'a 两次,就像在函数 中:
impl<'a> 定义了一个生命周期  'a ,而  Foo<'a> 使用它。

四、Lifetime要解决什么问题?

其中一个就是要解决悬垂指针“Dangling pointer”的问题,好比
fn dangling<'a>() -> &'a i32 {
    letlocal_var: i32 = 1;
    &local_var
}


五、关于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函数体。

相关文章
相关标签/搜索