前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Rust学习】05_引用与借用

【Rust学习】05_引用与借用

原创
作者头像
思索
修改2024-07-29 20:41:48
1380
修改2024-07-29 20:41:48
举报
文章被收录于专栏:Rust入门笔记

前言

在这章我们将开始学习Rust的引用和借用,它们是Rust中重要的概念,它们允许我们创建可变引用,以及创建不可变引用。

内容

引用和借用

在下面的示例中,我们必须将 String 返回给调用函数,以便在调用 calculate_length 后仍能使用 String,因为 String 被移动到了 calculate_length 内。

下面是如何定义并使用一个(新的)calculate_length 函数,它以一个对象的引用作为参数而不是获取值的所有权:

代码语言:rust
复制
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

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

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

首先,请注意变量声明和函数返回值中的所有元组代码都消失了。注意我们传递 &s1 给 calculate_length,同时在函数定义中,我们获取 &String 而不是 String。

这些 & 符号表示引用,它们允许您引用某个值,而无需获得其所有权。

<iframe id="embed\_dom" name="embed\_dom" frameborder="0" style="display:block;width:489px; height:275px;" src="https://www.processon.com/embed/66a2ecf083801d1c4851c52f?cid=66a2ecf083801d1c4851c532"></iframe>

代码语言:txt
复制
注意:与使用 & 引用相反的操作是 解引用(dereferencing),它使用解引用运算符,*。

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

代码语言:rust
复制
let s1 = String::from("hello");

let len = calculate_length(&s1);

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

同样,函数的签名用 & 来表明参数 s 的类型是引用。让我们添加一些解释性注释:

代码语言:rust
复制
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
    s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
  // 所以什么也不会发生

变量 s 有效的作用域与任何函数参数的作用域相同,但是当停止使用 s 时,引用指向的值不会被删除,因为它没有 s 的所有权。当函数将引用作为参数而不是实际值时,我们不需要返回值来归还所有权,因为我们从未拥有所有权。

我们将创建一个引用的行为称为 借用(borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。

如果我们尝试修改借来的变量,会发生什么呢?

代码语言:rust
复制
fn main() {
    let s = String::from("hello");
    change(&s);
}

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

现在我们来运行一下,这时候我们会得到一个错误:

代码语言:shell
复制
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
  --> src/main.rs:19:5
   |
19 |     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
   |
18 | fn change(some_string: &mut String) {
   |                         +++

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

可变引用

们只需进行一些小的调整来修改借来的值,这些调整使用可变引用:

代码语言:rust
复制
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

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

首先,我们必须将 s 改为 mut。然后必须在调用 change 函数的地方创建一个可变引用 &mut s,并更新函数签名以接受一个可变引用 some_string: &mut String。这就非常清楚地表明,change 函数将改变它所借用的值。

不过可变引用有一个很大的限制:在同一时间,只能有一个对某一特定数据的可变引用。尝试创建两个可变引用的代码将会失败:

代码语言:rust
复制
 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);

错误如下:

代码语言:shell
复制
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
  --> src/main.rs:37:14
   |
35 |     let r1 = &s; // no problem
   |              -- immutable borrow occurs here
36 |     let r2 = &s; // no problem
37 |     let r3 = &mut s; // BIG PROBLEM
   |              ^^^^^^ mutable borrow occurs here
38 |
39 |     println!("{}, {}, and {}", r1, r2, r3);
   |                                -- immutable borrow later used here

我们不能有一个可变的引用,因为我们有一个不可变的引用指向相同的值。

不可变引用的用户不希望值突然从他们下面改变出来!但是,允许多个不可变引用,因为任何只读取数据的人都无法影响其他任何人对数据的读取。

请注意,引用的范围从引入它的地方开始,一直持续到最后一次使用该引用。例如,此代码将进行编译,因为不可变引用的最后一次使用(println!)发生在引入可变引用之前:

代码语言:rust
复制
    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}");

不可变引用 r1 和 r2 的作用域在 println! 之后结束,它们最后使用的位置,即在创建可变引用 r3 之前。这些作用域不重叠,因此允许使用此代码:编译器可以看出在作用域结束之前的某个时间点不再使用引用。

尽管借用错误有时可能会令人沮丧,但请记住,这是 Rust 编译器尽早指出潜在的错误(在编译时而不是在运行时),并准确地告诉你问题出在哪里。这样,你就不必追踪为什么你的数据不是你想象的那样。

悬垂引用

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针(dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

让我们尝试创建一个悬垂引用,Rust 会通过一个编译时错误来避免:

代码语言:rust
复制
fn main() {
    let reference_to_nothing = dangle();
}

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

    &s
}

错误如下:

代码语言:shell
复制
error[E0106]: missing lifetime specifier
  --> src/main.rs:60:16
   |
60 | 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`
   |
60 | fn dangle() -> &'static String {
   |                 +++++++
help: instead, you are more likely to want to return an owned value
   |
60 - fn dangle() -> &String {
60 + fn dangle() -> String {
   |

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

代码语言:txt
复制
this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

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

代码语言:rust
复制
fn dangle() -> &String { // dangle 返回一个字符串的引用

    let s = String::from("hello"); // s 是一个新的字符串

    &s // 返回字符串s的引用
} // 这时候 s 的作用域结束,s 被丢弃,s 的内存被释放。
  // Danger!

因为 s 是在 dangle 函数内部创建的,所以当 dangle 的代码完成后,s 将被释放。当我们试图返回对它的引用。这意味着此引用将指向无效的 String。这可不对!Rust 不允许我们这样做。

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

代码语言:rust
复制
fn no_dangle() -> String {
    let s = String::from("hello");
    s
}

这样就没有任何错误了。所有权被移动出去,所以没有值被释放。

引用的规则

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

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。

接下来,我们将查看一种不同类型的引用:切片(slices)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 内容
    • 引用和借用
      • 可变引用
        • 悬垂引用
          • 引用的规则
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档