Rust Ownership

Table of Contents

1. Rust Ownership

1.1. move

三种和 ownership 有关的变量传递方法:

  1. move
  2. copy
  3. borrow

move 和 copy 都属于传值, borrow 属于传引用. 其中 move 对应着 bitwise copy, copy 对应 deep copy

rust 不会自动实现 copy, 所以默认情况都是使用 move, 即 bitwise copy

#[derive(Debug)]
// Test 默认为 move 语义
struct Test(i32, String);

fn get_data() -> Test {
    let ret = Test(1, "hello".to_owned());
    let px = &ret as *const _;
    let p1 = &ret.1 as *const _ as *const [u64; 3];
    // test 结构体在栈上, i32 成员在0x7ffc2ed2a280,string 结构体在
    // 0x7ffc2ed2a298, 所以 string 结构体的大小为 0x18 (24), 因为 string 有三个
    // i64成员 (pointer_to_heap, size, capacity), 大小刚好是 24
    println!("get_data");
    println!("{:?}", (px, p1));
    unsafe {
        println!("{:?}", *p1);
    }
    // string 值为 [140450239479816, 5, 5], 其中 140450239479816 是堆上的地址, 5, 5 是 size 和 capacity
    // 返回时发生了 move, 即 bitwise copy
    return ret;
}

fn main() {
    let x = get_data();
    let px = &x as *const _;
    let p1 = &x.1 as *const _ as *const [u64; 3];
    // (0x7ffc2ed2a3e0, 0x7ffc2ed2a3e0)
    // 由于 bitwise copy, px, p1 的址址都变化了
    println!("after return");
    println!("{:?}", (px, p1));
    unsafe {
        // 但 p1 的值没有变, 因为 bitwise copy 时 copy 了 p1 的值 (string 三元组)
        println!("{:?}", *p1);
    }
}

// (0x7ffc2ed2a280, 0x7ffc2ed2a298, 0x7ffc2ed2a280)
// [140450239479816, 5, 5]
// (0x7ffc2ed2a3e0, 0x7ffc2ed2a3e0)
// [140450239479816, 5, 5]
get_data
(0x7ffd55a61ab0, 0x7ffd55a61ab0)
[5, 110986105547648, 5]
after return
(0x7ffd55a61c50, 0x7ffd55a61c50)
[5, 110986105547648, 5]

即使对于简单的赋值, move 也是 bitwise copy, 而不是简单的 alias

struct Test(i32);
fn main() {
    let t = Test(10);
    println!("{:?}", &t as *const _);
    let pt = &t as *const _ as *const [i32; 1];
    unsafe {
        println!("{:?}", *pt);
    }
    let t2 = t;
    println!("{:?}", &t2 as *const _);
    let pt2 = &t2 as *const _ as *const [i32; 1];
    unsafe {
        println!("{:?}", *pt2);
    }
}
0x7ffd4c8304fc
[10]
0x7ffd4c8305ac
[10]

1.2. clone

clone trait 实现了 deep copy, 对 string 来说, 它的 clone 实现会复现堆上的 string, 而不仅仅是 string 三元组.

#[derive(Debug, Clone)]
struct Test(i32, String);

fn get_data() -> Test {
    let ret = Test(1, "hello".to_owned());
    let px = &ret as *const _;
    let p0 = &ret.0 as *const _;
    let p1 = &ret.1 as *const _ as *const [u64; 3];
    println!("{:?}", (px, p0, p1));
    unsafe {
        println!("{:?}", *p1);
    }

    return ret.clone();
}

fn main() {
    let x = get_data();
    let px = &x as *const _;
    let p1 = &x.1 as *const _ as *const [u64; 3];
    println!("{:?}", (px, p1));
    unsafe {
        println!("{:?}", *p1);
    }
}
(0x7ffc263500f8, 0x7ffc26350110, 0x7ffc263500f8)
[5, 107972209712000, 5]
(0x7ffc26350260, 0x7ffc26350260)
[5, 107972209712032, 5]

由于 string 实现了 clone trait, ret.clone() 返回的变量被 move 到调用者的 x 后, 看到的 string 在 heap 上的位置变化了.

1.3. copy

copy 相当于`自动 clone`, 即对应于实现了 copy trait 的变量 x, y=x 时相当于 y=x.clone()

实现 copy trait 会在不注意的情况下引用性能开销, 因为 string 只是实现了 clone, 并没有实现 copy.

需要注意的是, Copy trait 依赖于 Clone trait, 但 derive(Copy) 时使用的是 bit-wise clone, 即使有实现自定义的 clone

1.4. borrow

move, copy 即传值, borrow 即传引用.

shared reference 存在时原变量无法再被修改, 以避免 `concurrent modification`, 另外, reference 必须始终引用到有效的值, 被 borrow 的资源也无法被 move

  • 无法 move:

    #[derive(Debug)]
    struct Test(String);
    
    fn main() {
        let t = Test("hello".to_owned());
        let pt = &t;
        let x = pt.0;
        println!("{:?}", x);
    }
    // --> quickrun:7:13
    //  |
    //7 |     let x = pt.0;
    //  |             ^^^^
    //  |             |
    //  |             cannot move out of borrowed content
    //  |             help: consider borrowing here: `&pt.0`
    //
    //error[E0507]: cannot move out of `pt.0` which is behind a `&` reference
    
  • 无法修改:

    #[derive(Debug)]
    struct Test(String);
    
    fn main() {
        let mut t = Test("hello".to_owned());
        let pt = &t;
        t.0 = "world".to_owned();
        println!("{:?}", pt);
    }
    
    // error[E0506]: cannot assign to `t.0` because it is borrowed
    // --> quickrun:7:5
    // |
    // 6 |     let pt = &t;
    // |              -- borrow of `t.0` occurs here
    // 7 |     t.0 = "world".to_owned();
    // |     ^^^ assignment to borrowed `t.0` occurs here
    // 8 |     println!("{:?}", pt);
    // |                      -- borrow later used here
    //
    // error: aborting due to previous error
    //
    // For more information about this error, try `rustc --explain E0506`.
    

1.5. ownership

上面提到的 move, copy, borrow 与传统的变量传递方法没有什么区别. rust 的独特之处是在这之上的 ownership 机制:

  1. owner 是指变量所引用的资源的 owner
  2. owner 离开作用域时负责其资源的释放
  3. 为防止 double free, 每个资源只有一个 owner.
  4. bitwise copy 会共享相同的资源, 因此 bitwise copy 时 owner 会 move 到新的变量上
  5. borrow 不会拥有资源, 也就不会释放资源, 但 rust 编译器会保证被 borrow 的资源是有效的, 例如:

    • owner 的 life time 大于 borrow 的 life time, 防止 borrow 引用了已经释放的资源
    • 被 borrow 的资源无法被 move
    • 被 borrow 的资源无法被修改
    fn main() {
        let y: &String;
        {
            let x = "hello".to_owned();
            // y 并不是 x 的 owner, 但它的 life time 给 owner x 要长, 所以编译出错
            y = &x;
        }
    
        println!("{:?}", y);
    }
    
    //   error[E0597]: `x` does not live long enough
    //   --> quickrun:6:13
    //   |
    // 6 |         y = &x;
    //   |             ^^ borrowed value does not live long enough
    // 7 |     }
    //   |     - `x` dropped here while still borrowed
    // 8 |
    // 9 |     println!("{:?}", y);
    //   |                      - borrow later used here
    // // error: aborting due to previous error
    // // For more information about this error, try `rustc --explain E0597`.
    

1.6. slice

  1. slice 是 reference, 它们本身就是 borrow

    fn main() {
        let t = "hello".to_owned();
        let pt:&str = &t[1..2];
        // t 无法被 mov, 因为它已经被 pt borrow
        let _t2 = t;
        println!("{:?}", pt);
    }
    
  2. slice 实现上包含了额外信息: slice 的 ptr 指向原始数据中的起始位置, 另一个 len 成员表示 slice 的长度

    fn main() {
        let t = "hello".to_owned();
        let p1 = &t as *const _ as *const [u64; 3];
        unsafe {
            println!("{:?}", *p1);
        }
    
        let pt = &t[1..];
        println!("{:?}", pt);
        let p2 = &pt as *const _ as *const [u64; 2];
        unsafe {
            println!("{:?}", *p2);
        }
        let pt = &t[2..];
        println!("{:?}", pt);
        let p2 = &pt as *const _ as *const [u64; 2];
        unsafe {
            println!("{:?}", *p2);
        }
    }
    
    [5 (\, 106094169496448) (\, 5)]
    

1.7. mutable borrow

mutable borrow 的规则与读写锁类似:

  1. mutable borrow 只能有一个
  2. mutable borrow 不能与非 mutable borrow 共存
  3. 多个非 mutable borrow 可以共存
fn main() {
    let mut x = 10;

    let y1 = &mut x;
    println!("{:?}", y1);

    // 两个 &mut 不能同时有效
    let y2 = &mut x;
    println!("{:?}", y2);

    *y1 = 1;
    *y2 = 2;
}

1.8. 关于 borrow

extern void *get_buff();

int main(int argc, char *argv[]) {
    void *buff = get_buff();
    return 0;
}

谁负责 get_buff 释放 get_buff 返回的资源?

rust 的 borrow 规则决定了调用者不是资源的 owner, 所以对 rust 来说, 总是被调用者负责释放资源

fn get_buff() -> &str {
    let mut s = String::new();
    s.push_str("hello");
    return &s;
}

以上代码无法编译, 因为 get_buff 返回后 s 即被释放. 需要给返回的引用找一个 lifetime 足够长的 owner, 例如:

fn get_buff2() -> &'static str {
    let s = "hello";
    return s;
}

fn get_buff<'a>(s: &'a str) -> &'a str {
    return s;
}

1.9. 总结

  1. move 即 bitwise copy
  2. copy 即 deep copy
  3. borrow 即 reference
  4. move 导致 owner 改变
  5. borrow 不改变 owner, 但需要确保被 borrow 的数据有效: life time 足够长, 不能被 move
  6. mutable borrow 的规则类似于读写锁

Author: [email protected]
Date: 2018-12-27 Thu 00:00
Last updated: 2024-08-12 Mon 11:12

知识共享许可协议