rust 所有权,借用,生命周期

 

一,前言

            三个概念,一直在阶段性的困扰我,感觉还是尝试自己说一下,再加点案例,能够把这些讲清楚后,会有一个清晰的认识吧。

二,所有权

            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 中较短的那个生命周期结束之前保持有效。

 
 

 

猜你喜欢

转载自blog.csdn.net/qq_39308071/article/details/113759658