一文解读rust“所有权”和“借用”概念

107 阅读4分钟

所有权

对于所有权, rust给出下面3条规定:

  • Each value in Rust has a variable that’s called its owner
  • There can only be one owner at a time
  • When the owner goes out of scope, the value will be dropped

翻译一下就是,

  • 每一个值(value)都有一个所有者
  • 同一时刻只能拥有一个所有者, 所以存在move语义
  • 当所有者离开作用域, 它的值就会就会被丢弃

为了理解上面的三条规则, 我写了一段代码:

fn main() {
    //1. 每一个值都拥有一个所有者
    let data = vec![1, 2, 3, 4];
    
    //所有权转移, 只能有一个所有者
    let data1 = data;

    let ans = sum(data1); //传参时候所有权转移

    println!("the sum of data: {}", ans);
}

fn sum(data : Vec<u32>) -> u32 {
    data.iter().fold(0, |acc, x| acc + x)
}

rust通过这种所有权的转移规则可以避免堆上数据多重引用的存在, 但是这也无法避免的引入了一定的复杂度, 因为有些时候我们需要手动复制, 会非常麻烦,所以rust也提供了copy语义

  1. 如果一个数据结构实现了copy语义, 那么在赋值或者传递参数的时候, 值会自动按位拷贝
  2. 如果你不希望值的所有权被转移, 又无法实现copy语义, 可以借用数据

值的借用

在rust中, 如果你想让一个值的所有权不发生转移, 但是又无法实现copy语义, 那么你就可以使用借用语义, 借用语义和其它编程语言中的引用很类似, 就是一种变量别名。

在Java中传递一个整数, 是传递值, 也就是copy语义, 如果传递的是堆上的数据, 那么就会隐式的传递引用。在rust中你必须显式的将某个数据的引用传递给另外一个函数。同时在默认情况下面, rust的借用是只读的。rust的引用实现了copy 语义, 所以在发生参数传递的时候会将引用拷贝一份。

fn main() {
    let data = vec![1, 2, 3, 4];
    
    //借用拥有所有权, 传递引用但是使用copy语义, 
    let data1 = &data;

    //传递引用使用的是copy语义
    println!("sum of data:{}", sum(data1));

    //只是借用, 所有权并没有发生转移
    println!("the indenx 1 of data {}", data[1]);
}

fn sum(data : &Vec<u32>) -> u32 {
    data.iter().fold(0, |acc, x| acc+x)
}

对引用的约束

我们对值的借用也要有约束, 这个约束就是: 借用不可以超过值的生存周期, 我在这里说一个很好理解这句话的方法, 值要不然放在栈, 要不要不然放在堆上, 放在堆上的东西, 往往会有一个胖指针指向它, 所以堆上的数据生存周期是和栈上的生命周期是息息相关的, 所以我们只需要关注栈上面的声明周期就行了。

为了你的理解我画了一张图片

fn main() {
    
    let mut data : Vec<&u32> = Vec::new();
    //data和v的生命周期是不一致的
    push_local_ref(&mut data);
    
    //data 和a的声明周期是一样的
    let a = 1;
    data.push(&a);

    //声明周期不一致, b的声明周期和a的生命周期是不同的 
    let _b = local_ref();

}

fn local_ref<'a>() -> &'a u32 {
    let a = 1;
    &a
}


fn push_local_ref(data : &mut Vec<&u32>) {
    let v = 42;
    data.push(&v);
}

可变引用

没有引入可变引用之前, 因为同一个值同一个时刻只有一个所有者, 所以如果要修改这个值, 只能通过唯一的一个所有者进行, 如果允许改变值本身, 会带来新的问题。

  1. 多个引用共存
fn main() {
    let mut data = vec![1, 2, 3, 4];

    //破坏了循环的可变性
    for item in data.iter_mut() {
        //在遍历的过程中同修改值
        data.push(*item + 1);
    }
}
  1. 一个可变引用和多个只读引用
fn main() {
    let mut data = vec![1, 2, 3];
    let data1 = vec![&data[0]];

    println!("data[0]:{:p}", &data[0]);

    for i in 0..100 {
        data.push(i);
    }

    println!("data[0]: {:p}", &data[0]);    
    println!("boxed: {:p}", &data1);
}

但是这样依然存在问题, 当值的所有者进行内存扩容的时候, 原来的内存空间被释放, 而新的内存空间被分配, 但是只读引用指向的内存空间就失效了, 导致内存安全问题。

对于这两种问题, rust同时也做出限制, 限制如下:

  1. 在同一个作用域中, 仅仅允许一个活跃的可变引用
  2. 在一个作用域内,活跃的可变引用(写)和只读引用(读)是互斥的,不能同时存在
fn main() {
    let mut data = vec![1, 2, 3];
    
    let data_ptr = &data;

    //活跃引用
    for i in 0..100 {
        data.push(i);
    }

    println!("data[0]: {}", data_ptr[0]);
}