当前位置: 首页 > 知识库问答 >
问题:

生锈中的生命周期如何影响可变性?

宰父焕
2023-03-14

我正在通过显式注释函数签名来测试我对Rust中生命周期的理解,我创建了一个我不确定是否理解的示例。

在本例中,我模拟了共享一本书并在其中翻开一页的概念。为此,我使用一个可变引用,将其传递给一个函数,该函数更新书籍结构的当前页面字段。MyBook结构和主功能如下所示:

#[derive(Debug)]
pub struct Book<'a> {
    pub title: &'a str,
    pub curr_page: Option<i32>,
    pub page_count: i32,
}

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

fn main() {
    let mut the_book: Book = Book {
        title: "The Book",
        curr_page: None,
        page_count: 104,
    };

    let a_book: &mut Book = &mut the_book;

    borrow_and_read(a_book);
    borrow_and_read(a_book);

    observe_book(&*a_book);
}

pub fn observe_book<'a>(a_book: &'a Book<'a>) {
    println!("Observing: {:?}", a_book);
}

(操场)

对于borrow_and_read函数的第一个实现,我让编译器添加注释和编译的所有内容:

fn borrow_and_read(a_book: &mut Book) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

然后,我尝试添加一个生命周期注释,为Book本身的引用和实例指定生命周期:

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

这产生了以下错误:

error[E0499]: cannot borrow `*a_book` as mutable more than once at a time
  --> src/main.rs:25:21
   |
24 |     borrow_and_read(a_book);
   |                     ------ first mutable borrow occurs here
25 |     borrow_and_read(a_book);
   |                     ^^^^^^
   |                     |
   |                     second mutable borrow occurs here
   |                     first borrow later used here

error[E0502]: cannot borrow `*a_book` as immutable because it is also borrowed as mutable
  --> src/main.rs:27:18
   |
24 |     borrow_and_read(a_book);
   |                     ------ mutable borrow occurs here
...
27 |     observe_book(&*a_book);
   |                  ^^^^^^^^
   |                  |
   |                  immutable borrow occurs here
   |                  mutable borrow later used here

在仔细考虑了我最初尝试的内容后,我决定将对书籍的可变引用的生命周期与书籍本身的实例相分离是有意义的。然后我想到了这个:

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

它编译并输出预期的结果。

我很困惑,为什么我最初的错误信息是一本书被多次易变地借用了。我想我可以传递一个可变引用,因为引用的每次使用都理解引用是可变的。这一想法似乎得到了我的借入和读取功能的最终实现的证实,但我不完全确定为什么指定书籍实例的生存期比可变引用的生存期长,其中“b:”a解决了我的问题。

我希望对可变引用和书籍实例使用相同的生存期如何产生我得到的错误有一个坚实的理解。

共有1个答案

何修能
2023-03-14

您的原始版本的问题是生命周期太受限制。通过使Book上的借阅与书名上的借阅具有相同的长度("The Book"),可变借阅被迫与实际书籍本身一样长,这意味着它永远不能不可变地借阅。

让我们来探讨一下。检查您的固定版本会更容易,然后查看原始版本是如何约束它的。

fn borrow_and_read<'a, 'b>(a_book: &'a mut Book<'b>) 
where 'b : 'a {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

此函数有两个生存期参数:一个用于书本本身,另一个用于书本上的可变借阅。我们还限制了“b:”a,这意味着任何具有生存期的借款a的有效期不超过具有生存期的借款b的有效期。这实际上是多余的,因为编译器无论如何都可以看到这一点。通过使用类型为

现在让我们看一下main。我们将在书本身上调用生存期。我们将调用这本书的可变借阅mtb的生存期。最后,我们将调用不可变借阅(位于observe\u book'imb。让我们看看每个人的一生要持续多久。

// Initialize `the_book`. 'book has to start before this.

// Mutably borrow `the_book`. 'mtb has to start here.
let a_book: &mut Book = &mut the_book;

// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);
// Use the mutable borrow. 'mtb has to still be valid.
borrow_and_read(a_book);

// Deref the mutable borrow and reborrow immutably.
// 'imb has to start here, so 'mtb has to end here.
// 'imb is a reference to `the_book`, so 'book has to still be active.
observe_book(&*a_book);

// The variables are no longer needed, so any outstanding lifetimes can end here
// That means 'imb and 'book end here.

因此,这里的问题关键在于,通过这种设置,'mtb必须在'book之前结束。现在让我们看看函数的原始版本。

fn borrow_and_read<'a>(a_book: &'a mut Book<'a>) {
    match a_book.curr_page {
        Some(page) => a_book.curr_page = Some(page + 1),
        None => a_book.curr_page = Some(0),
    };
}

现在我们只有一个生存期参数,它强制标题的生存期和可变借用的生存期相同。这意味着'mtb'book必须是相同的。但是我们刚刚展示了'mtb必须在'book之前结束!因此,由于这种矛盾,编译器给了我们一个错误。我不知道为什么错误是不能借用*a_book一次多次可变的技术细节,但我想编译器认为变量的“用法”类似于我们谈论生命周期的方式。由于'book必须持续到调用observe_book'mtb'book相同,因此它将'book的用法视为可变借用的用法。同样,我对此并不完全确定。可能值得提交一个问题,看看消息是否可以改进。

我确实在上面撒了一点谎。虽然Rust不进行隐式类型强制,但它确实进行了终身强制。寿命较长的借款可以被强制为寿命较短的借款。这在这里最终并不重要,但值得了解。

这本书的标题是字符串文字,其类型为<代码>

当我们调用单参数版本的“借阅”和“阅读”时,必须将“书本”和“mtb”强制降低到较短的通用寿命。(在这种情况下,由于《mtb》一书中提到了mtb,所以mtb就可以工作了——事实上,这是最长的使用寿命)。对于双参数版本,无需强制<可以按原样使用代码>'book和mtb

现在,当我们derefa_book并不可变地重新借用它时,任何可变借用都不能被激活。这意味着mtb'book'mtb都被强制结束的较短生命周期。但是a_book有生命周期'book并且我们正在使用它,所以'book不能结束。因此出现了错误。

使用双参数版本,'book不会强制缩短生命周期,因此它可以继续。

 类似资料:
  • 下面是一个资源借用的例子: fn main() { let a = 100_i32; { let x = &a; } // x 作用域结束 println!("{}", x); } 编译时,我们会看到一个严重的错误提示: error: unresolved name x. 错误的意思是“无法解析 x 标识符”,也就是找不到 x , 这是因为像很多编

  • 每个响应对象只有当在 servlet 的 service 方法的范围内或在 filter 的 doFilter 方法范围内是有效的,除非该组件关联的请求对象已经开启异步处理。如果相关的请求已经启动异步处理,那么直到AsyncContext 的 complete 方法被调用,请求对象一直有效。为了避免响应对象创建的性能开销,容器通常回收响应对象。在相关的请求的startAsync 还没有调用时,开发

  • 注:本文档提供的生命周期指的是 Universal App 的生命周期,它依赖 rax-app 提供的 runApp方法。 App 级生命周期 launch  在 App 启动时触发 使用生命周期 你可以使用 rax-app 提供的 useAppLaunch 来注册 App 级别的生命周期。 示例: import { useAppLaunch } from 'rax-app'; useAppLa

  • 我们大致为WebAPplication设计了4个生命周期: 请求初始化其实就是从URL中解析提取出{module}, {action}, {method}; 然后再根据{module}, {action}, {method}找到对应的Controller文件; 然后再调用对应的{method},完了之后再发送响应。当然响应的过程中肯定是要顺带着解析下模板标签啦。 恩,这就完了,貌似感觉很简单啊。

  • 如下图. 可以看出,基本周期是: created mounted updated (update 可以理解成人肉手动操作触发) destroyed 上面步骤中的 1,3,4都是自动触发。 每个步骤都有对应的 beforeXyz方法 所以, 我们一般使用mounted 作为页面初始化时执行的方法

  • 概览 组件的生命周期分为三个阶段:挂载、渲染、卸载,下图展示了解组件在整个生命周期中所涉及到的方法调用、原型方法调用和状态变化。 挂载阶段 从组件实例被创建再到被插入根组件树中,所经历的操作如下: 初始化组件实例。 根据组件类型绑定对应的原型。 调用 proto->init() 原型方法。 标记组件需要刷新全部样式。 因父组件变为另外一个组件,触发 link 事件。 更新阶段 当组件被插入到根组件