在结构的字段中使用引用时是否有任何限制?

Are there any restrictions when using a reference in a field of a struct?

我正在尝试了解 Rust 中的生命周期如何影响结构。附件是一个让我感到困惑的最小示例。

我想给一个结构体一个对象的引用,然后需要更改这个对象。由于无法在借用对象时更改对象,因此我认为我必须在更改它时删除引用。所以我的想法是将变量定义为一个选项,并通过将其设置为 None 来在修改对象期间删除引用。对于不使用 Struct 的示例,这似乎可行。 但是,如果我现在将此引用放入 Struct 中,它将不起作用。在我看来,借用检查器忽略了变量 string 不再被借用的事实。有没有办法仍然通过结构实现所需的行为?

这个有效(link to playground):

fn main() {
    let mut string = "mutable string".to_string();
    let mut ref_to_string: Option<&str> = Some(&string);
    ref_to_string = None;
    string = "mutated string".to_string();
    ref_to_string = Some(&string);
}

然而,这不起作用link to playground:

struct Foo<'a> {
    pub ref_data: Option<&'a str>,
}

fn main() {
    let mut string = "mutable string".to_string();
    let mut foo = Foo{ref_data: Some(&string)};
    foo.ref_data = None;
    string = "mutated string".to_string();
    foo.ref_data = Some(&string);
}

错误信息:

error[E0506]: cannot assign to `string` because it is borrowed
  --> src/main.rs:11:5
   |
9  |     let mut foo = Foo{ref_data: Some(&string)};
   |                                      ------- borrow of `string` occurs here
10 |     foo.ref_data = None;
11 |     string = "mutated string".to_string();
   |     ^^^^^^ assignment to borrowed `string` occurs here
12 |     foo.ref_data = Some(&string);
   |     ---------------------------- borrow later used here

For more information about this error, try `rustc --explain E0506`.

问题是整个 Foo 在整个生命周期内都是通用的。这样,当您分配 foo = Foo {ref_data: Some(&string)} 时,foo 的类型是 Foo<'lifetime_of_string>。您可以将 foo.ref_data 设置为 None 任意设置,但就借用检查而言,仍然存在生命周期与 string 重叠的活动对象。为此,只要foo活着,string就被借了。唯一的方法是删除整个 foo:

let mut string = "mutable string".to_string();
let mut foo = Foo{ref_data: Some(&string)};
drop(foo);
foo = Foo { ref_data: None };
string = "mutated string".to_string();
foo.ref_data = Some(&string);

问题是您引用了 string 变量两次。借用第一个时绑定 &str 生命周期至少为 foo。引用另一个存在于“另一个”变量中的字符串应该没有问题:

fn main() {
    let string = "mutable string".to_string();
    let mut foo = Foo {
        ref_data: Some(&string),
    };
    foo.ref_data = None;

    let string = "mutated string".to_string();
    foo.ref_data = Some(&string);
}

Playground

注意使用另一个let绑定而不是改变变量。

秘密在于Foo结构的定义:

struct Foo<'a> {
    pub ref_data: Option<&'a str>,
}

更具体地说,在 'a 生命周期说明符中。通过指定 Foo 的寿命为 'a 并且 ref_data 具有相同的寿命 (&'a str),您说您的 &str 的寿命与 Foo。所以只要 Foo 还活着,编译器就不能删除对 string.

的引用

不幸的是,只有在字段为 Some.

时,无法指定该结构的生命周期为 'a

要改变结构引用的对象,您可以:

  1. 将对象的所有权转移到结构,然后从结构中获取对它的可变引用。
  2. 使用内部可变性(参见 RefCell)。
  3. 在变异之前删除你的结构,并在变异完成后重新创建它。

哪种方法更好取决于您的具体用例。