参考资料和借用

示例 4-5 中元组代码的问题在于,我们必须将 返回给调用函数,这样我们仍然可以在 调用 ,因为 已移动到 中。相反,我们可以提供对该值的引用。 引用就像一个指针,因为它是我们可以遵循的地址来访问 存储在该地址的数据;该数据由其他变量拥有。 与指针不同,引用保证指向 particular 类型。StringStringcalculate_lengthStringcalculate_lengthString

下面介绍如何定义和使用具有 将对象作为参数引用,而不是获取值的所有权:calculate_length

文件名: src/main.rs
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

首先,请注意,变量声明中的所有元组代码和 函数返回值消失。其次,请注意,我们传入 并且,在其定义中,我们采用而不是 。这些 & 符号表示引用,它们允许您引用 到某种价值,而无需拥有它的所有权。图 4-6 描述了这个概念。&s1calculate_length&StringString

三个表:s 的表仅包含指向该表的指针
对于 S1。s1 的表包含 s1 的堆栈数据,并指向
string 数据。

图 4-6:指向字符串 s1&String 关系

注: 与 using 引用相反的是 dereferencing,即 使用取消引用运算符 完成。我们将看到 dereference 运算符,并在 第 15 章.&*

让我们仔细看看这里的函数调用:

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

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

该语法允许我们创建一个引用 value of 但不拥有它的引用。因为它不拥有它,所以它指向的值将 当引用停止使用时,不会被删除。&s1s1

同样,该函数的签名用于指示 参数是引用。让我们添加一些解释性注释:&s

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

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
  // it refers to, it is not dropped.

变量有效的范围与任何函数相同 parameter 的范围,但引用指向的值不会被删除 when 停止使用,因为没有所有权。When 函数 将引用作为参数而不是实际值,我们不需要 返回值以归还所有权,因为我们从未有过 所有权。sss

我们将创建引用的作称为 借用。就像在现实生活中一样,如果 人拥有某样东西,你可以向他们借用。完成后,您将拥有 归还它。你不拥有它。

那么,如果我们试图修改我们借用的东西会发生什么呢?在 示例 4-6.剧透警告:它不起作用!

文件名: src/main.rs
fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
示例 4-6:尝试修改借用的值

错误如下:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn change(some_string: &mut String) {
  |                         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

正如变量默认是不可变的一样,引用也是如此。我们不是 允许修改我们引用的内容。

可变引用

我们可以修复示例 4-6 中的代码,以允许我们修改借用的值 只需进行一些小的调整,即可使用可变引用

文件名: src/main.rs
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

首先,我们将 更改为 。然后,我们创建一个可变引用,其中包含我们调用函数的位置,并将函数签名更新为 接受带有 .这使得它非常 clear 函数将改变它借用的值。smut&mut schangesome_string: &mut Stringchange

可变引用有一个很大的限制:如果你有一个对 值,则不能有对该值的其他引用。此代码 尝试创建两个可变引用将失败:s

文件名: src/main.rs
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}

错误如下:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

这个错误说这段代码是无效的,因为我们不能借用 as mutable 一次多次。第一个可变借款是 in 和 must last 直到它在 中使用,但在创建该 mutable reference 及其用法,我们尝试创建另一个 mutable reference 中,它借用的数据与 .sr1println!r2r1

阻止对 Same Time 允许突变,但以非常可控的方式。这是某种东西 新的 Rustacean 很难处理,因为大多数语言都允许你 mution 随时。拥有此限制的好处是 Rust 可以 防止在编译时发生数据争用。数据争用类似于争用 条件,并在发生以下三种行为时发生:

  • 两个或多个指针同时访问相同的数据。
  • 至少有一个指针用于写入数据。
  • 没有使用任何机制来同步对数据的访问。

数据争用会导致未定义的行为,并且可能难以诊断和修复 当你试图在运行时追踪他们时;Rust 通过以下方式防止此问题 拒绝编译数据争用的代码!

与往常一样,我们可以使用大括号来创建新的范围,从而允许 多个可变引用,只是不是同时引用:

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

    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.

    let r2 = &mut s;
}

Rust 对组合可变和不可变引用执行类似的规则。 此代码会导致错误:

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{}, {}, and {}", r1, r2, r3);
}

错误如下:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

呼!我们也不能有一个可变的引用,而我们有一个不可变的引用 设置为相同的值。

不可变引用的用户不希望该值突然改变 从他们下面!但是,允许多个不可变引用,因为没有 一个只是读取数据的人有能力影响其他人的 读取数据。

请注意,引用的范围从引入它的地方开始并继续 到上次使用该引用的时间。例如,这段代码将 compile 的 , 发生在引入可变引用之前:println!

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{r1} and {r2}");
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{r3}");
}

不可变引用的范围 和 结束于它们最后一次使用的位置之后,即可变引用之前 创建。这些范围不会重叠,因此允许使用以下代码:编译器可以 告诉 reference 在 结束 之前的某个点不再使用 范围。r1r2println!r3

尽管借用错误有时可能令人沮丧,但请记住,这是 Rust 编译器提早指出潜在的错误(而不是在编译时 比在运行时)显示问题所在。那你就不 必须追踪为什么您的数据不是您想象的那样。

悬空引用

在带有指针的语言中,很容易错误地创建一个 dangling pointer— 引用内存中可能已 given to someone other - 通过释放一些内存,同时保留指向该内存的指针 记忆。相比之下,在 Rust 中,编译器保证引用将 永不悬空引用:如果您有对某些数据的引用,则 编译器将确保数据不会超出范围 引用数据。

让我们尝试创建一个悬空引用,看看 Rust 如何使用 编译时错误:

文件名: src/main.rs
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

错误如下:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
5 | fn dangle() -> &'static String {
  |                 +++++++
help: instead, you are more likely to want to return an owned value
  |
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
  |

error[E0515]: cannot return reference to local variable `s`
 --> src/main.rs:8:5
  |
8 |     &s
  |     ^^ returns a reference to data owned by the current function

Some errors have detailed explanations: E0106, E0515.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 2 previous errors

此错误消息引用了我们尚未介绍的功能:lifetimes。我们将 在第 10 章中详细讨论寿命。但是,如果您忽略了这些部分 关于 lifetimes,该消息确实包含了为什么此代码有问题的关键:

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

让我们仔细看看代码的每个阶段到底发生了什么:dangle

文件名: src/main.rs
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle returns a reference to a String

    let s = String::from("hello"); // s is a new String

    &s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
  // Danger!

因为是在 内部创建的,所以当代码完成时,将被解除分配。但我们试图返回对它的引用。这意味着 此引用将指向无效的 .那不好!锈 不允许我们这样做。sdangledanglesString

这里的解决方案是直接返回:String

fn main() {
    let string = no_dangle();
}

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

这工作没有任何问题。所有权已移出,但没有任何内容 已解除分配。

参考规则

让我们回顾一下我们讨论过的关于引用的内容:

  • 在任何给定时间,您都可以有一个可变引用或任何 不可变引用的数量。
  • 引用必须始终有效。

接下来,我们将看看另一种引用:slices。

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准