乐趣区

关于前端:Rust入门系列之引用和借用

1. 援用

很多场景中,咱们可能只是想读取某个变量指向的值,并不想取得其所有权,这个时候就能够应用 援用。其实在很多其余的编程语言中,也有援用的概念。

  • 简略来讲,援用是创立一个变量,指向另个指针的地址,而不是间接指向 该指针指向的堆内存地址
  • 通过 & 取地址符获取对一个指针变量的援用

例如在 Rust 中,咱们这样创立援用:

let s1 = String::from("hello");

// 获取 s1 的援用
let s = &s1;
  • 下图就很好的示意了 援用 的关系

    • 变量 s1 是栈内存中的一个指针地址,通过 ptr 记录了存储于堆内存中的 String(“hello”) 的地址
    • 变量 s 也存在栈内存中,通过 ptr 记录了 s1 的指针地址,来实现对 String(“hello”) 的援用

2. 借用

应用 援用 作为函数参数的行为被称作 借用,如何应用 借用 来躲避某个变量的所有权产生挪动,咱们能够看以下例子:

fn main() {let s = String::from("hello!");

    // 应用 s 的援用作为入参,s 的所有权就不会产生挪动
    let len = get_length(&s);

    println!("the length of {} is {}", s, len); // the length of hello! is 6
}

fn get_length(string: &String) -> usize {
    // 援用和变量一样,默认也是不可变的
    // string.push_str("world"); // `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

    string.len()}

3. 可变援用

从下面的例子咱们能够晓得,援用和变量一样,默认也是不可变的,咱们不能批改援用所指向的值。但如果想要这么做,那么能够应用 mut 关键字,将援用转为可变的:

fn main() {let mut s = String::from("hello!");

    /*
     * 应用 mut 关键字,将 s 的可变援用作为入参,* 这样 s 的所有权既不会产生挪动,函数中也能通过 可变援用 来批改 s 的值
     */
    let len = get_length(&mut s);
    
    // 在 get_length 函数中咱们实现了对 s 的批改
    println!("the length of {} is {}", s, len); // the length of hello!world is 11
}

fn get_length(string: &mut String) -> usize {
    // 通过可变援用对值进行批改
    string.push_str("world");
    string.len()}

3.1 可变援用的重要限度

  • 对应一个指针变量,在一个特定的作用域内,只能有一个可变援用。起因也很好了解,如果在一块作用域内,当一个变量存在两个可变援用,那就意味着同一时间可能有两个变量管制着同一块内存空间,就会产生数据竞争,很容易在运行时产生 bug,因而 Rust 通过编译时查看,来躲避这样的问题呈现

3.1.1 同一作用域,只能存在一个可变援用

通过例子能够看到,当咱们对 变量 origin 进行了两次可变援用,编译时就间接报错

fn main() {let mut origin = String::from("hello");

    let ref1 = &mut origin;
    let ref2 = &mut origin; // error: cannot borrow `origin` as mutable more than once at a time

    println!("{}, {}", ref1, ref2);
}

3.1.2 不可变援用能够有多个

如果是同时应用多个不可变援用,则不会有这个限度,因为不可变援用其实就是只读的,不存在可能的内存平安危险。通过这个例子咱们能够看到,Rust 是容许这样应用的:

fn main() {let origin = String::from("hello");

    let ref1 = &origin;
    let ref2 = &origin;

    println!("{}, {}", ref1, ref2);
}

3.1.3 某些场景下,能够存在多个可变援用

从下面的两个例子咱们曾经晓得,如果一个作用域内同时存在两个以上的可变援用,那么就可能产生数据竞争,那么是否存在某些场景,会呈现多个可变援用呢?咱们看上面这个例子:

  • 其实在程序执行过程中,只有走出作用域,那么作用域中的变量就会被开释,这个例子中当申明 ref2 时,ref1 曾经被销毁了,所以还是能够保障可变援用的第一条准则

    fn main() {let mut origin = String::from("hello");
    
      {
          let ref1 = &mut origin;
          
          println!("{}", ref1);
      }
    
      // 当 ref2 申明时,ref1 曾经被销毁了
      let ref2 = &mut origin;
    
      println!("{}", ref2);
    }

3.1.4 不能同时领有一个可变援用和一个不可变援用

对于一个指针变量,它的可变援用和不可变援用其实是互斥的,不能同时存在。起因很简略,可变援用能够批改指向内存空间的值,当值被批改,不可变援用的意义也就不存在了。因而 Rust 在编译时会进行查看,发现这种状况则会间接报错:

fn main() {let mut origin = String::from("hello");

    let ref1 = &origin;
    let ref2 = &mut origin;// cannot borrow `origin` as mutable because it is also borrowed as immutable

    println!("{} {}", ref1, ref2);
}

4. 悬垂援用

这一种呈现在 C/C++ 等语言中的 bug 场景,形容的是这样一种场景,一个变量所指向的内存空间曾经被开释或调配给其余程序应用,但这个变量任然无效。在 Rust 中,编译器会查看这种场景,来避免出现悬垂援用。假如编译通过,上面就是一个会产生悬垂援用的场景:

fn main() {let s = String::from("haha");

    // s 的所有权被挪动到 helper 的作用域中
    let a = helper(s);

    /*
     * 假如编译通过:* 当 helper 调用结束,s 指向的内存空间就被开释了,但在 helper 中返回了 s 的援用,* 其实这个援用曾经生效,这时 变量 a 就变为了一个 悬垂援用
     */
}

fn helper(s: String) -> &String {println!("{}", s);
    &s // error: missing lifetime specifier
}

但其实在编译时,Rust 就不会容许 helper 返回 s 的援用

退出移动版