• rust_3_所有权


    所有权系统的设计目标:跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间

    所有权规则

    1. Rust 中的每一个值都有一个被称为其 所有者owner)的变量。
    2. 值在任一时刻有且只有一个所有者。
    3. 当所有者(变量)离开作用域,这个值将被丢弃。

    首先:

    ​ 字符串字面值,属于硬编码,即编译期已经编译进代码段,不会随着程序运行而改变,至于其具体类型,Rust也没说。

    let s = "hello";

    普通变量的生命周期和作用域和c++类似。遗憾的是这里的s不是普通变量,ta是个slices类型,天生的引用类型,引用的生命周期和c++不一样,所有权章节会遇到。

    String类型作为复杂类型的代表,何谓复杂类型,理解上存在非POD类型的数据,或者从c++语义上理解就是需要运行时做额外操作的,比如分配内存,比如初始化虚表。

    {
       let s = String::from("hello"); // 从此处起,s 是有效的
    
       // 使用 s
    }                                  // 此作用域已结束,
                                      // s 不再有效
    

    Rust在这一点上像把所有变量变成了unique_ptr对象,只在定义的局部有效,超过作用域就要自动被释放。

    s 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String 的作者可以放置释放内存的代码。Rust 在结尾的 } 处自动调用 drop。这点就像C++的栈上对象离开作用域自动调用析构函数。

    变量与数据交互的方式(一):移动

    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}, world!", s1); //编译错误
    
    error[E0382]: use of moved value: `s1`
     --> src/main.rs:5:28
      |
    3 |     let s2 = s1;
      |         -- value moved here
    4 |
    5 |     println!("{}, world!", s1);
      |                            ^^ value used here after move
      |
      = note: move occurs because `s1` has type `std::string::String`, which does
      not implement the `Copy` trait
    
    

    其实理解上就是:Rust 永远也不会"自动"创建数据的 “深拷贝”,为此设计了一种新的语义,即一旦需要拷贝,那么,拷贝的源变量将会被标记为一个无效变量,不可再使用,当然也不需要再释放(drop),而其所有权转移到了新的拷贝目标变量上。

    这一点,其实为了规避堆上数据重复(深拷贝)的同时又规避double free的一种设计方案,但给编码人员带来的麻烦就是,要明确知道s1是什么类型才能确定后续是否可以继续使用s1。

    变量与数据交互的方式(二):克隆

    确实 需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的通用函数。

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

    rust在设计上,从语言语义的角度规避了c++语言使用过程中,可能存在的堆内存使用问题,但也会给出另一种方案来解决堆内存使用的部分场景(比如用户期望深拷贝)。

    如果一个类型拥有 Copy trait,一个旧的变量在将其赋值给其他变量后仍然可用。换句话说类型的copy trait注解决定了是否适用这个所有全转移。

    如下:

    • 所有整数类型,比如 u32
    • 布尔类型,bool,它的值是 truefalse
    • 所有浮点数类型,比如 f64
    • 字符类型,char
    • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32)Copy 的,但 (i32, String) 就不是。

    基本数据类型变量存在于栈上,默认深拷贝,默认有 Copy trait

    Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait,即只要成员或自身自定义了drop析构函数那么这个类型就无法使用copy注解

    任何简单标量值的组合可以是 Copy 的,不需要分配内存或某种形式资源的类型是 Copy

    整体而言,就是如果一个变量实现深拷贝需要额外操作,那么就不能使用copy注解。copy注解理论上只能用在变量可以完全bytecopy的情形。

    但实际上,即使自身涉及到资源的申请和释放,只要不定义drop trait 其自身应该还是可以使用copy trait,但要自己保证资源的合理销毁(比如申请和释放资源通过其他接口,而非构造或者析构来申请或者释放),这是个人理解,待确认。

    只在栈上的数据:拷贝

    完全支持copy trait,不会发生所有权转移。

    所有权与函数

    fn main() {
        let s = String::from("hello");  // s 进入作用域
    
        takes_ownership(s);             // s 的值移动到函数里 ...
                                        // ... 所以到这里不再有效
    
        let x = 5;                      // x 进入作用域
    
        makes_copy(x);                  // x 应该移动函数里,
                                        // 但 i32 是 Copy 的,所以在后面可继续使用 x
    
    } // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
      // 所以不会有特殊操作
    
    fn takes_ownership(some_string: String) { // some_string 进入作用域
        println!("{}", some_string);
    } // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
    
    fn makes_copy(some_integer: i32) { // some_integer 进入作用域
        println!("{}", some_integer);
    } // 这里,some_integer 移出作用域。不会有特殊操作
    

    返回值与作用域

    返回值也可以转移所有权

    fn main() {
        let s1 = gives_ownership();         // gives_ownership 将返回值
                                            // 移给 s1
    
        let s2 = String::from("hello");     // s2 进入作用域
    
        let s3 = takes_and_gives_back(s2);  // s2 被移动到
                                            // takes_and_gives_back 中,
                                            // 它也将返回值移给 s3
    } // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
      // 所以什么也不会发生。s1 移出作用域并被丢弃
    
    fn gives_ownership() -> String {             // gives_ownership 将返回值移动给
                                                 // 调用它的函数
    
        let some_string = String::from("hello"); // some_string 进入作用域.
    
        some_string                              // 返回 some_string 并移出给调用的函数
    }
    
    // takes_and_gives_back 将传入字符串并返回该值
    fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
    
        a_string  // 返回 a_string 并移出给调用的函数
    }
    

    引用与借用

    我们将获取引用作为函数参数称为 借用borrowing

    fn main() {
        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 语法让我们创建一个 指向s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域时其指向的值也不会被丢弃。

    这里定义

    fn calculate_length(s: &String) -> usize
    

    及调用

    let len = calculate_length(&s1);
    

    都需要使用 & 个和c++有点差异,c++调用是时这样传表示s1的地址,即指针。

    如果我们尝试修改借用的变量,需要定义可变引用

    可变引用

    fn change(some_string: &mut String) {
        some_string.push_str(", world");
    }
    

    Rust 可以在编译时就避免数据竞争

    可变引用有一个很大的限制:在特定作用域中的特定数据只能有一个可变引用。

    let mut s = String::from("hello");
    {
        let r1 = &mut s;
    } // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
    let r2 = &mut s;
    

    也 不能在拥有不可变引用的同时拥有可变引用

    let mut s = String::from("hello");
    
    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 大问题
    
    println!("{}, {}, and {}", r1, r2, r3);
    

    一个引用的作用域从声明的地方开始一直持续到最后一次使用为止

    所以如下代码是可以编译的:

    let mut s = String::from("hello");
    
    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    println!("{} and {}", r1, r2);
    // 此位置之后 r1 和 r2 不再使用
    
    let r3 = &mut s; // 没问题
    println!("{}", r3);
    

    悬垂引用(Dangling References)

    fn dangle() -> &String { // dangle 返回一个字符串的引用
        let s = String::from("hello"); // s 是一个新字符串
        &s // 返回字符串 s 的引用
    } // 这里 s 离开作用域并被丢弃。其内存被释放。
      // 危险!
    

    所谓的悬垂引用和c++的悬垂指针意义差不多,都是引用或者指向了一个无效数据。

    从C++上说即返回了局部变量的引用。

    fn no_dangle() -> String {
        let s = String::from("hello");
        s
    }
    

    我们只能通过不返回引用的方式来返回局部变量,类似于c++的拷贝构造,不过rust中叫所有权转移。结果就是走到 }时不再析构s(调用s.drop)。

    引用的规则

    让我们概括一下之前对引用的讨论:

    • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
    • 引用必须总是有效的。
  • 相关阅读:
    file_put_contents实现内容追加
    Laravel5.5 实现session配置
    easywechat在laravel框架中的应用-代码参考
    Laravel框架模型层表单验证
    工厂方法模式及php实现
    简单工厂模式及php实现
    Actionscript,AS3,MXML,Flex,Flex Builder,Flash Builder,Flash,AIR,Flash Player之关系
    php之依赖注入和控制反转
    php pdo操作数据库
    odbc连接数据库
  • 原文地址:https://www.cnblogs.com/kuikuitage/p/14233348.html
Copyright © 2020-2023  润新知