Rust所有权

Rust所有权

Rust没有类似于JVM这种GC的自动回收内存解决方案,而是提供了所有权,借用和生命周期来实现内存安全。所有权时Rust内存安全的核心原则,引用和借用时对语言类型系统的拓展。(为了避免混淆,此篇文章只涉及所有权的讲解)

资源所有者

这里的所有者指的是引用在堆和栈上保存值的任何变量或者时包含打开文件描述符,数据库连接套接字,网络套接字以及类似内容的变量。资源所有者的一个重要职责就是明智的释放它们使用的内存。

在C++中,存在多个变量指向堆上的某个值是没有问题的,这就是所谓的别名。然而,当给定作用域中资源存在多个不可变别名和至少一个可变别名时就会出现问题。

在Rust中,资源只有一个所有者,就是创建它们的变量。

Rust资源所有权定义

使用let语句创建值或者资源,并将其分配给变量时,该变量将成为值或资源所有者。

将变量重新分配给另一个变量时,值或资源的所有权将转移至另一个变量中,原来的变量将失效

在变量作用域的末尾资源会被释放,变量也会失效

下面示例演示了这一点:

#[derive(Debug)]
struct Foo(u32);
fn main() {
    let foo=Foo(2048);
    println!("{:?}",foo);
    let bar=foo;
    println!("{:?}",bar);
    println!("{:?}",foo);
}

该代码编译将会报如下错误:

error[E0382]: borrow of moved value: `foo`
 --> src/main.rs:8:21
  |
4 |     let foo=Foo(2048);
  |         --- move occurs because `foo` has type `Foo`, which does not implement the `Copy` trait
5 |     println!("{:?}",foo);
6 |     let bar=foo;
  |             --- value moved here
7 |     println!("{:?}",bar);
8 |     println!("{:?}",foo);
  |                     ^^^ value borrowed here after move

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership`

意思是在把foo赋值给bar语句执行后,第一行Foo实例的所有权被转移给了bar,此时foo是没有拥有任何内容的,所以foo就不能再被使用了。

通过上述实例我们能够看出,Rust中是不允许有多个变量拥有同一资源的所有权的,即不能有别名

作用域以及释放时机

和其他C系列代码一样,Rust中变量的作用域时由一对花括号表示的。无论何时使用代码块都会创建一个作用域。此外,作用域支持嵌套,并且可以在子作用域中访问父作用域的元素,但反过来不行。下面例子演示了作用域的嵌套关系:

fn main() {
    let level_0_str=String::from("foo");
    {
        let level_1_number=9;
        {
            let mut level_2_vector=vec![1,2,3];
            level_2_vector.push(level_1_number);//可以访问父作用域的内容
        }//level_2_vector离开作用域
        // level_2_vector.push(4);//level_2_vector不再有效
    }//level_1_number离开作用域
    // println!("{}",level_1_number);//level_1_number不再有效
    println!("{}",level_0_str);
}//level_0_str离开作用域

作用域是一个非常重要的属性,它也会被用来推断后面介绍的借用和生命周期。当作用域结束时,拥有值的任何变量都会运行相关代码来取消分配该值,并且其自身在作用域之外是无效的。特别是对在堆上分配的值,drop方法会被放在作用域结束标记"}"之前调用。drop方法来自Drop特征,它被Rust中大部分堆分配类型实现,可以轻松的自动释放资源。

移动和复制语义

来看下面一段代码:

fn main(){
    let foo=1234;
    let bar=foo;
    println!("foo={}",foo);
    println!("bar={}",bar);
}

它是可以正常执行的,这好像与我们之前的所有权规则好像不符,其实这是因为复制语义的存在。前面我们展示的所有权转移的例子是移动语义(move),即把一个变量赋值给另一个变量会同时转移所有权。但是如果实际的实例是有复制语义的,那么情况就会有所变化。

通过Copy特征可以为类型提供复制语义,基本类型和其他仅适用于栈的数据类型以及&T(不可变引用)在默认情况下实现了Copy特征,这就是上述i32类型具有复制语义的原因。

我们也可以手动给类型实现Copy特征以提供复制语义:

#[derive(Debug, Clone, Copy)]
struct Foo(u32);
fn main() {
    let foo = Foo(2048);
    println!("{:?}", foo);
    let bar = foo;
    println!("{:?}", bar);
}

上述代码是可以正常工作的。因为Clone是Copy的父级特征,所以任何实现了Copy特征的类型必须要实现Clone特征,这也是上面添加了Clone特征的原因。

移动时机

1.将变量赋值给其他变量

2.将变量(而不是引用)传递给函数

#[derive(Debug)]
struct Foo(u32);
fn main(){
    let foo=Foo(1);
    print(foo);
    // println!("{:?}",foo);//value borrowed here after move
}
fn print(foo:Foo){
    println!("{:?}",foo);
}

3.从函数中返回变量

#[derive(Debug)]
struct Foo(u32);
fn main(){
    let foo=Foo(1);
    let foo=print_and_back(foo);
    println!("{:?}",foo);
}
fn print_and_back(foo:Foo)->Foo{
    println!("{:?}",foo);
    foo
}

4.表达式

for 迭代循环

#[derive(Debug)]
struct Foo(u32);
fn main(){
    let foos:Vec<Foo>=vec![Foo(1),Foo(2)];
    for foo in foos{
        println!("{:?}",foo);
    }
    // println!("{:?}",foos[0]);//value borrowed here after move
}

match 中涉及提取值

#[derive(Debug)]
enum Food{
    Cake,
    Pizza,
    Salad,
}
#[derive(Debug)]
struct Bag{
    food:Food,
}
fn main(){
    let bag=Bag{food:Food::Cake};
    match bag.food {
        Food::Cake => {},
        a=>println!("I'got {:?}",a),
    }
    // println!("{:?}",bag);//value borrowed here after partial move
}

5.impl块中的含有self(不是&self或&mut self)参数的函数

#[derive(Debug)]
struct Foo(u32);
trait Bar{
  fn print(self);
}
impl Bar for Foo{
    fn print(self) {
        println!("{:?}",self);
    }
}
fn main(){
    let foo=Foo(1);
    foo.print();
    // println!("{:?}",foo);//value borrowed here after move
}

6.闭包

对于没有Copy语义的类型,在闭包中未显式指明move关键字但是闭包中有move操作或是显式指明move关键字后使用的闭包外变量都会被move到闭包中。

#[derive(Debug)]
struct Foo(u32);
fn main(){
    let foo=Foo(1);
    let closure=||{
        println!("{:?}",foo);
    };
    closure();
    let foo=Foo(1);
    let closure=move||{
        println!("{:?}",foo);
    };
    closure();
    // println!("{:?}",foo);//value borrowed here after move
    let foo=Foo(1);
    let closure=||{
        let a=foo;
        println!("{:?}",a);
    };
    closure();
    // println!("{:?}",foo);//value borrowed here after move
}

对于具有Copy语义的类型来说,闭包又有如下表现:

fn main(){
    let a=1;
    let closure=||{
        println!("{}",a);//1
    };
    closure();
    println!("{}",a);//1
    let a=1;
    let closure=||{
        let mut b=a;
        b=15;
        println!("{}",a);//1
    };
    closure();
    println!("{}",a);//1
    let mut a=1;
    let mut closure=||{
        a=a+1;
        println!("{}",a);//2
    };
    closure();
    // println!("{}",a);/2
    let mut a=1;
    let mut closure=move||{
        a=a+1;
        println!("{}",a);//2
    };
    closure();
    println!("{}",a);//1
}

上述例子表明,没有显式声明move时,闭包内部使用的就是外部的变量,如果显式声明了move,闭包中会重新生成一个单独的同名变量供闭包内使用。