美文网首页Rust探索
Rust所有权,可转可借

Rust所有权,可转可借

作者: 袁承兴 | 来源:发表于2020-07-14 20:27 被阅读0次
    ownership

    《释放堆内存,Rust是怎么做的?所有权!》一文中,我们看到了Rust的不凡身手:只要跳出具有所有权的变量作用域,那么该变量所拥有的堆上内存,就会进行确定性的释放。

    {
     let  v: Vec<u8>  =  vec![0;100];
    } // v作为数组的所有者,在离开作用域时,销毁了所持有的内存。
    

    这时候,问题来了,如若不能跨越作用域,那么充其量也就是另一种局部变量而已。堆变量的生命周期如何才能跨越作用域呢?

    答案是:通过所有权的转移和借用

    所有权的转移

    赋值即转移(move)

    实现Rust所有权的转移,非常简单,赋值即转移

    {
      let v: Vec<u8> = vec![0;100];
      let u = v; // 数组所有权由v转移给u
      u //函数结尾没有分号,即代表return
    } // u作为数组的所有者(如果未发生转移),在离开作用域时,销毁了所持有的数组内存。
    

    let w = get_vector() // 函数返回变量,再次把数组的所有权转移给w</pre>

    上面的示例代码,发生了两次堆上数组所有权的转移:

    1. u8类型的数组在函数内部从堆上申请;

    2. 一开始数组的所有权属于变量v;

    3. 当v赋值给u时,数组的所有权转移到了u;

    4. 当函数返回时,通过赋值给w,数组的所有权发生了第二次转移;

    最终通过函数返回值赋值操作,将堆所有权转移到了原作用域之外的变量。

    避免野指针

    此时,我们问个问题,在函数内部,当v赋值给u,转移数组所有权后,v此时的状态是什么?

    回答之前,先复习下Rust所有权的基本特性:

    • Rust中的每个值都有一个对应的变量作为它的所有者;

    • 在同一时间内,只有且仅有一个所有者;

    • 当所有者离开自己的作用域时,它持有的值就会被释放掉。

    {
      let v: Vec<u8> = vec![0;100];
      let u = v; // 数组所有权由v转移给u
      println!("{}", v[0]);
    }
    

    上述代码,数组所有权由v转移给u后,再去使用v,编译都无法通过,编译器会提示:

    error[E0382]: borrow of moved value: v

    可见此时的v,已经被废弃了,所以当v离开作用域时,也不会清理任何堆数据。

    Rust所有权的唯一性,在编译期就避免了C++的野指针和二次释放。

    赋值转移的本质

    Rust赋值的本质,包含两件事:

    • 浅拷贝,变量数据指向堆的数据,并未发生变化;

    • 废弃源变量,这是Rust独有的;

    所有权借用

    借用的使用场景

    通过所有权转移,函数传参也可以把所有权传递至函数内部。但是通过转移,源变量就被废弃了,如果在函数调用后还想继续使用源变量,则可以使用借用的方式:

    {
      let s1 = String::from("hello");
      let len = calculate_length(&s1); // 借用,而不是转移
      println!("The length of '{}' is {}", s1, len);
    }
    
    fn calculate_length(s: &String)->usize{
      s.len()
    }// s1离开作用域,堆数据释放
    

    所有权的借用,是通过引用实现的。

    顾名思义,通过借用得到的对堆数据的引用,是没有所有权的。借用者离开自己的作用域,当然也不会发生对堆数据的释放。

    借用与归还

    借用分为两种:

    • 不可变借用,借来,但不能改,通过引用实现;

    • 可变借用,借来,可以改,通过可变引用来实现;

    {
     let mut x = String::from("Hello");
     x.push_str(", world");
     let r1 = &x; // 不可变借用
     let r2 = &mut x; // 可变借用
     let r3 = &mut x; // 可变借用
     r3.push_str("!"); // 修改
     println!("r3: {}", r3);
    }
    

    在2020年6月的第一版第一次印刷的中文版《Rust权威指南》,第4章,认识所有权,97页提到:

    可变引用在使用上有一个很大的限制:对于特定作用域中的特定数据来说,一次只能声明一个可变引用。

    但是在我的环境里,rustc 1.44.0 (49cae5576 2020-06-01),这个限制明显放开了一些,上面的代码在我的环境里是可以成功编译和运行的。

    “非词法作用域生命周期”的延伸阅读:[译] Rust - None Lexical Lifetimes (NLL) 使用指南

    除了借用这个概念,我还归纳了一个概念来解释——归还

    • 借用后,在当前作用域中,最后一次使用,即等于归还;

    • 即便在同一个作用域内,借用后只要归还,就能再次借用;

    • 借用期间,源变量不能修改;

    • 可变借用期间,源变量不可用;

    这个借用和归还,就像真实世界里图书馆里的一本书。

    Rust之所以要做这些约束,主要是为了解决数据竞争(data race),避免了:两个或两个以上的指针同时访问同一空间时,其中至少有一个指针会向空间中写入数据,且没有同步数据访问的机制。

    相关文章

      网友评论

        本文标题:Rust所有权,可转可借

        本文链接:https://www.haomeiwen.com/subject/avtqhktx.html