UP | HOME

Rust核心概念:所有权,引用和借用

为什么需要所有权(Ownership)

Rust的核心概念之一即为所有权的概念,所有权的概念帮助编译器 在编译时(Compiler time)确立规则保证资源的管理,从而不会在 运行时(runtime)造成类似于垃圾回收的产生的性能开销.

所有权规则(Ownership rule)

  1. 在Rust中的每个值附属于一个'所有者'的变量
  2. 任何时候每个值只能拥有一个'所有者'
  3. 当'所有者'离开作用域(scope),值将会被销毁

以String为例

{
    let s = "Hello world";                /// s refs to string literal(s -> &str), which was hard coded into binary by compiler
                                          /// and was immutable
    let mut s1 = String::("hello")        /// s1 was a mutable string and owns heap-allocated object. 
}

一个String对象由3部分组成,从 fig-1可知,第一部分是一个指针,这个指针指向的堆内存保存着 字符串的内容,另外两个部分是字符串的长度(length)和容量(capacity)。并且这三个部分都是组合存在于栈上的。

trpl04-01.svg

Figure 1: s1的内存布局

考虑下面的代码

{
    let s1 = String::("world");
    let s2 = s1
}

s2=s1 操作完成之后,Rust为了保证资源的安全, s1 不再是一个合法的引用,资源的所有权将会被转移到 s2 上,之后任何对 s1 的操作将会被编译器视为非法从而无法通过编译。如果我们想要拷贝堆上的字符串资源要该怎么样操作呢?显然Rust为我们提供 了类似于C++中深拷贝的方法: clone

{
    let s1 = String::("world");
    let s2 = s1.clone();
}

clone 方法的使用实际上是在堆上面新建了一个对象并且拷贝了原来对象的值到新建的对象之上,因此当你看见 clone 被调用时, 你应该提醒自己这里执行了代价昂贵的操作。

函数和所有权

向函数传递参数类似于用值给变量赋值,同样会发生所有权的转移。反过来,函数的返回值同样也会发生所有权的转移。

fn main(){
    let s1 = get_ownership();                          ///s1从返回值中获得了所有权

    let s2 = String::from("rose");                    

    let s3 = takes_and_gives_back(s2);                 ///s3从返回值中获得了所有权
}

fn get_ownership() -> String{
    let some_string = String::from("jack");           
    some_string
}

fn takes_and_gives_back(a_string: String) -> String{  ///s2 move 到了新变量,失去了了所有权
    a_string
}

什么是引用

rust官方文档中对引用没有严格的定义,其实可以理解为在变量或对象面前加上C语言的取地址符&,我们即可代表 这是一个对原变量/对象的引用。引用允许你获取某些值但你对这些值并不持有所有权,因为引用不持有所有权,因此当引用离开作用域的时 候,它引用的值或对象不会被drop或者析构。

fn main(){
    let s = String::from("Hello");
    let len = calculate_length(&s1);
}

fn calculate_length(s: &String) -> usize{ /// s是一个对String 对象的引用
    s.len()
}

/// 因为s是一个引用,即使s离开了作用域,也不会引起任何其引用对象的析构,这里除了返回指定值
/// 不会有任何事情发生

什么是借用

有了引用概念的铺陈,借用的概念就更简单了。我们称将引用作为函数参数的行为叫做借用。通常情况下类似 于现实生活中如果一个人拥有某样物品,你可以向他借用这件物品,当你使用完毕,你也必须返给拥有者。

可变引用和可变借用

struct Point {x: i32, y: i32, z: i32} 
{
    let mut point = Point{x: 0, y: 0, z: 0};
    {
        let borrowed_point = &point;          /// 不可变引用
        let mutable_borrow = &mut point;      /// 可变引用
        ...
    }
}

可变引用可以利用引用来修改引用对象的值,不可变引用则不可以,但是试着想象一下,如果一个对象存在可变 引用,却被多个函数同时借用,那么这样的行为是不安全的。因此Rust对此作出了约定,如果某个资源被可变的 借用了,那么如果还需要发生借用行为,那么必须等到当前借用离开作用域之后才可以发生。