Rust 所有权(Ownership)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

Rust的所有权概念是其最独特的特性之一,它对Rust的其它部分也有很大影响。所有权概念让Rust在没有GC的情况下,也能够保证内存安全(无需手动管理内存CC++)。

GC就是garbage collection,比较直观,JavaGo等均需要。
内存安全广义讲应该包括没有野指针、内存重复释放等错误。进一步的,在rust中结合其它特性可以实现多线程的并发安全访问,CC++常见的内存泄露则不属于不安全(虽然也是问题)。


一、Ownership是什么?

概括的讲,所有权是rust管理内存的一个规则集。
众所周知,计算机程序都需要管理内存,只是不同的编程语言采用了不同的方法。主要分两类:

  • 不断检测未使用内存并释放的GC(Java/go…)
  • 程序员手工管理内存(c/c++…)

而Rust采用第三种方法:通过一个包含规则集的所有权系统来管理内存。编译器会检查程序的内存使用是否符合规则集中的规则,违反规则的程序将无法编译通过。另外,这些规则的校验不会带来执行期的开销(编译期检查,编译很慢)。

规则有没有存在漏洞的可能性?导致内存不安全呢?目前看还没有

虽然对很多人来讲,所有权是一个新概念,需要时间来适应。但是,理解所有权是理解Rust独特性的基础。

二、规则集

所有权的规则集主要包括三条,可以类比C++中的RAIIunique_ptr等概念。

  • 每个值都有一个称之为owner的变量(有点类似C++ new完必须记得delete)
  • 任一时刻,每个value只有一个owner(即不可共享,实际多线程是可以共享的,不过需要用到unsafe等特性)
  • 程序执行出了owner作用域,对应的值会被丢弃(类似C++自动调用析构函数)

三、理论+示例

a. 简单情况

扫描二维码关注公众号,回复: 14605762 查看本文章
    {
        let s = String::from("hello"); // s is valid from this point forward
        // do stuff with s
    }                                  // this scope is now over, and s is no longer valid

以上为例,当s的作用域结束之时,即可以将s拥有的值所占用的堆(heap)空间释放。这个机制与C++的RAII( Resource Acquisition Is Initialization)类似,Rust通过调用drop,而C++调用析构函数。

b. 复杂情况

    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}, world!", s1);

对于涉及到堆内存的对象,Rust默认采用浅复制。此外,s2 = s1执行之后,在rust中,s1将不再指向任何String对象,s2单独拥有字符串hello。图中的情况,其实更符合C++中的shared_ptr赋值后(引用计数增加)。
浅复制

解决了问题,和C++中的unique_ptr类似,shared_ptr的需求通过RcArc等特性解决。

深复制需要显式声明:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);
}

上述s1.clone()中的clone方法即是深拷贝,当然前提和C++一样,你得正确实现相应的函数,rust 中就是Clone trait。
深复制

Copy trait? 有什么意义吗?因为所有类型的对象都可以copy吧?

函数调用的实参,函数返回值,均会转移对象的所有权。

四、引用和借用

引用并没有对应值的所有权,因此不会触发调用drop。引用可以成为借用。

多个变量指向同一个对象,没有共享问题?单线程下不会,多线程下则不允许。

同一个对象不能同时被借用多次,下面代码编译错误。

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}

这个限制可以在编译期消除数据竞争。

但是,对象已经可以被两个变量修改了啊?可变引用变量+原来的变量,这两者的竞争如何解决呢,多线程不允许共享,如果需要共享,则需要用其它机制。

不可变引用可以有多个,但是可变引用只能有一个。当不可变引用完全无用之前,不能有新的可变引用。(所谓的共享不可变,可变不共享

编译期检测虚悬引用(dangling)
引用规则:

  • 任一时刻,只能由一个可变引用、或者若干不可变引用
  • 引用必须一直有效

五、切片类型(slice)

避免下面这种问题,s.clear()之后,s[word]访问其实已经非法,运行期报错。

thread ‘main’ panicked at ‘byte index 5 is out of bounds of ``’, src/main.rs:20:18

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); // word will get the value 5
    s.clear(); // this empties the String, making it equal to ""
    // word still has the value 5 here, but there's no more string that
    // we could meaningfully use the value 5 with. word is now totally invalid!
}


总结

初看下来,Rust实现内存安全的机制,并没有什么高深的。其实就是可变不共享,共享不可变

猜你喜欢

转载自blog.csdn.net/wenyuanhust/article/details/123546156