zoukankan      html  css  js  c++  java
  • Rust-Lang Book Ch.4 Ownership

    Ownership

    Ownership使得Rust能够无需额外的garbage collector线程就确保内存安全。在编译时,Rust就通过一系列规则并确定Ownership。Ownership与Borrowing, slices和Rust在内存中如何排列数据有关。

    在许多编程语言中,数据在stack上还是在heap上不会对性能有很大影响。但是在Rust中则不然。存在stack中的数据必须已知具体所占空间大小,否则就应该放到heap上。Stack与Heap在性能上的不同主要有如下两点:1. 推送数据到Stack上要比在heap分配空间要更快,这是因为allocator不需要去寻找能够容纳当前数据的地址。2. 在stack中获取数据要比在heap中更快。这是因为heap可能要通过不紧邻的指针来取值

    Ownership Rules

    Rust中的每个值都有一个管理其生命周期的变量称为Owner。同一时间只能有一个Owner。当Owner出了作用域后,该值就从内存中抹去。

    Variable Scope

        {                      // s is not valid here, it’s not yet declared
            let s = "hello";   // s is valid from this point forward
    
            // do stuff with s
        }                      // this scope is now over, and s is no longer valid
    

     

    以String为例阐释Rust的OwnerShip机制

    string literal所占空间已知,同时数值已知,但是是immutable的。可改的String变量必须要从string literal转化而来。显而易见的,string literal的具体内容编译的时候就已知了,所以Rust就直接将literal放入到最终的目标格式文件中,因此,string literal本身能够保持快速+高效。不过,这也导致了这些literal都是immutable的。

    而在String这个类型中,则能够支持可变的text,编译器会在heap上分配所需空间,而这些在编译的时候是未知的。为此,这些memory需要被memory allocator在运行的时候请求,并且当这个String变量出了作用域之后被释放掉。具体地,Rust会在Variable出了作用域之后调用一个名为drop的函数。drop函数的基本模式与c++的RAII(Resource Acquisition Is Initialization,资源获取即是初始化)十分相似。

    String具体由三部分构成: 1. 一个指向内容所在内存的指针 2. 内容的长度 3. 目前的容量capacity。对于`let s2 = s1`这样一句话,不会发生内容的拷贝,只会发生指针的拷贝。在指针拷贝后,这片内存就有多个值同时使用,当出了作用域之后,也就会引发两次free。如何避免double free呢?1. 在s2=s1这句之后,编译器会标记s1是个无效变量,如果这之后再去用s1,Rust会直接报错:borrow of moved value: `s1`。2. s2=s1时发生了Move(类似Shadow Copy)

        let s1 = String::from("hello");
        let s2 = s1;
    
        println!("{}, world!", s1);
    //^^ value borrowed here after move

    如果希望Deep Copy这个String,那么我们应该调用clone方法。要注意clone方法相对于move更耗时耗空间。

    String类型的变量s1是空间未定且存储在heap上的,而对于空间大小已经由类型给定,且存储在stack上的变量,Copy是可以轻松进行的,也即此时Move(shadow copy)和Clone(deep copy)本质上是相同的。例如let i1=5;let i2 = i1;就不需要invalidate i1。Rust有一个特别的Annotation名为Copy,可以让类似Integer的类型自动拷贝赋值,而且不会使得赋值后旧的变量变得无效。注意如果要实现Copy的类型不能有任何成员实现了Drop特性,如果实现了Drop特性,也即要求该类型在出作用域时做出什么特定行为,就会得到编译错误。注意Tuples如果成员都是可以copy的,那么Tuple本身也可以Copy。

    Ownership and Functions

    传参这一行为和赋值也非常相似,也就是说,将一个变量传入、传出函数就会发生move或者copy这种行为。如果将s1: String传入一个函数,并且在这之后仍然要使用s1,编译器就会报错。如果一个变量包含在heap上的数据出了作用域,那么在heap上的数据就会被清理掉,除非这个变量之前被moved,并且将ownership转交给了另外一个变量。

    那么如果我们希望一个函数使用一个变量,但是不要发生ownership的转移呢?传入之后再把所有变量返回并且赋值实在是太麻烦了。这时,我们可以使用reference,即引用,符号&。

    References and Borrowing

    像c语言一样,Reference使用符号&,而Dereference使用符号*。引用本身相当于一个指向变量的指针,使得变量本身的控制权不会发生改变。Rust称使用references来作为函数参数的行为为borrowing,即借用。

    fn calculate_length(s: &String) -> usize { // s is a reference to a String
        s.len()
    } // Here, s goes out of scope. But because it does not have ownership of what
      // it refers to, nothing happens.
    

      

        let s1 = String::from("hello");
    
        let len = calculate_length(&s1);
    

      

    要区分&String与c++中的reference。c++中的reference是能够修改变量内容的,而Rust中的&String则仅是不会发生所有权的Move。如果想要修改变量内容,依然要加上mut这个关键字,使之成为可修改的引用,即必须传给函数&mut String类型的变量,函数才能修改String而且不发生变量所有权转让。注意mutable reference有个限制,那就是在一个作用域中,一次只能存在一个有效的mutable reference。(难道mutable reference是单例的?)

    let r1 = &mut s;
             ------ first mutable borrow occurs here
    let r2 = &mut s;
             ^^^^^^ second mutable borrow occurs here
    println!("{}, {}", r1, r2);
                      -- first borrow later used here
    

      

    此外,immutable reference和mutable reference也存在如下限制:要么同时多个用于读的immutable reference,要么单个mutable reference。

        let mut s = String::from("hello");
    
        let r1 = &s; // no problem
        let r2 = &s; // no problem
        let r3 = &mut s; // BIG PROBLEM
    
        println!("{}, {}, and {}", r1, r2, r3);

    这样限制的目的是为了避免data race。

    Data race发生在如下场景:

    1. 多个指针同时使用同块数据。

    2. 至少其中一个执行写操作

    3. 程序员没有附加该如何处理这种读写冲突或者写写冲突的说明

    这种data race可能导致非常隐秘的bug,所以Rust直接规定只准有一个mutable reference,只准有一个指针写这块数据,从根本上避免冲突。

    不过,reference用过了不再使用就可以声明新的,并不是要一直出了作用域才能声明新的。

        let mut s = String::from("hello");
    
        let r1 = &s; // no problem
        let r2 = &s; // no problem
        println!("{} and {}", r1, r2);
        // r1 and r2 are no longer used after this point
    
        let r3 = &mut s; // no problem
        println!("{}", r3);
    

      

    在Rust中,编译器保证不会产生Dangling Reference,也即在数据出了作用域之后,reference本身也就不能再使用,否则编译器报错。

    fn main() {
        let reference_to_nothing = dangle();
    }
    
    fn dangle() -> &String {
                         ^ help: consider giving it a 'static lifetime: `&'static`
        let s = String::from("hello");
    
        &s
    }
    

      

    Slice类型

    Slice在传入传出的时候是无需转换Ownership的。Slice使得你能够使用一个集合中的部分元素,而不需要直接传入整个集合。

    String的slice的类型是&str。

    fn first_word(s: &String) -> &str {
        let bytes = s.as_bytes();
    
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
    
        &s[..]
    }
    
    fn main() {
        let mut s = String::from("hello world");
    
        let word = first_word(&s);
    
        s.clear(); // error!
    //^^^^^^^^^ mutable borrow occurs here
    
        println!("the first word is: {}", word);
    //                                             ---- immutable borrow later used here
    
    }
    

      

    编译器会保证引用类型-也即这里的tuple,在作用期间保持有效。如果将String s的slice赋给word这个变量,那么在word使用完毕结束生命周期之前,s本身就不能再改动。

    这里要说明的是,对于let s = "Hello world!"这样一句赋值,s也是一个slice指向内存中的这块string literal。所以s的类型就是&str。

    此外,在只读的函数first_word中,与其传入&String,不如传入&str,这样就能够在string slice(和string literal)上进行操作,而String转为string slice不耗费什么时间空间,string slice转化为String却是相反的。

    //fn first_word(s: &String) -> &str {
    //改为
      fn first_word(s: &str) -> &str {
    

      

        let word = first_word(&my_string_literal[..]);
    

      

    其他类型的slice,作为array的一部分,通常类型名称为&[T],例如&[i32]。所有slice的操作和原理都一样,存储第一个元素和其总长。

  • 相关阅读:
    BNUOJ 12756 Social Holidaying(二分匹配)
    HDU 1114 Piggy-Bank(完全背包)
    HDU 2844 Coins (多重背包)
    HDU 2602 Bone Collector(01背包)
    HDU 1171 Big Event in HDU(01背包)
    HDU 2571 命运 (入门dp)
    HDU 1069 Monkey and Banana(最长递减子序列)
    HDU 1160 FatMouse's Speed (最长上升子序列)
    HDU 2594 KMP
    POJ 3783 Balls --扔鸡蛋问题 经典DP
  • 原文地址:https://www.cnblogs.com/xuesu/p/13861015.html
Copyright © 2011-2022 走看看