【问题标题】:Is it possible to have a struct which contains a reference to a value which has a shorter lifetime than the struct?是否有可能拥有一个包含对生命周期比该结构短的值的引用的结构?
【发布时间】:2017-08-16 12:15:40
【问题描述】:

这是我要存档的简化版本:

struct Foo<'a> {
    boo: Option<&'a mut String>,
}

fn main() {
    let mut foo = Foo { boo: None };
    {
        let mut string = "Hello".to_string();
        foo.boo = Some(&mut string);
        foo.boo.unwrap().push_str(", I am foo!");
        foo.boo = None;
    } // string goes out of scope. foo does not reference string anymore

} // foo goes out of scope

这显然是完全安全的,因为一旦 string 超出范围,foo.boo 就是 None

有没有办法告诉编译器?

【问题讨论】:

  • 如果您尝试复制可以在托管语言中工作的东西,引用计数和weak (non-owning) references 可以让您模拟这样的东西。但是安全性将在运行时强制执行,而不是由编译器执行。
  • 这里提前定义foo的目的是什么?
  • @Matthieu M. 还有其他结构字段,我在性能很重要的循环中执行此操作。在这个例子中它完全没用:D
  • @trentcl 虽然这与我的情况无关,因为我实际上不只是出于性能原因创建新结构的唯一原因,但这似乎仍然很有趣。您介意使用“Rc”或弱引用来发布答案吗?如果你不这样做,我会稍微研究一下,并在我有时间时创建一个自我答案。

标签: struct rust lifetime


【解决方案1】:

这显然是完全安全的

对人类来说显而易见的东西对编译器来说并不总是显而易见的;有时编译器不如人类聪明(但它更加警惕!)。

在这种情况下,您的原始代码在启用non-lexical lifetimes 时编译:

#![feature(nll)]

struct Foo<'a> {
    boo: Option<&'a mut String>,
}

fn main() {
    let mut foo = Foo { boo: None };
    {
        let mut string = "Hello".to_string();
        foo.boo = Some(&mut string);
        foo.boo.unwrap().push_str(", I am foo!");
        foo.boo = None;
    } // string goes out of scope. foo does not reference string anymore

} // foo goes out of scope

这是 only 因为 foo 一旦无效(在 string 超出范围之后)就永远不会使用,不是 因为您将值设置为None。尝试在最内层范围之后打印出值仍然会导致错误。

是否有可能有一个结构包含对一个比结构更短的生命周期的值的引用?

Rust 借用系统的目的是为了确保持有引用的事物不会比被引用的项目寿命更长。

在非词汇生命周期之后

也许,只要您不使用不再有效的引用。这有效,例如:

#![feature(nll)]

struct Foo<'a> {
    boo: Option<&'a mut String>,
}

fn main() {
    let mut foo = Foo { boo: None };
    // This lives less than `foo`
    let mut string1 = "Hello".to_string();
    foo.boo = Some(&mut string1); 
    // This lives less than both `foo` and `string1`!
    let mut string2 = "Goodbye".to_string();
    foo.boo = Some(&mut string2); 
}

在非词法生命周期之前

没有。借用检查器不够聪明,无法告诉您在引用无效后不能/不使用该引用。太保守了。

在这种情况下,您会遇到这样一个事实,即生命周期被表示为类型的一部分。换句话说,通用生命周期参数 'a 已经被“填充”了一个具体的生命周期值,覆盖了 string 存在的行。但是,foo 的生命周期比这些行长,因此您会收到错误消息。

编译器不会查看您的代码执行的操作;一旦它看到你用那个特定的生命周期参数化它,它就是这样。


我通常的解决方法是将类型分成两部分,需要引用的部分和不需要引用的部分:

struct FooCore {
    size: i32,
}

struct Foo<'a> {
    core: FooCore, 
    boo: &'a mut String,
}

fn main() {
    let core = FooCore { size: 42 };
    let core = {
        let mut string = "Hello".to_string();
        let foo = Foo { core, boo: &mut string };
        foo.boo.push_str(", I am foo!");
        foo.core        
    }; // string goes out of scope. foo does not reference string anymore

} // foo goes out of scope

注意这如何消除了对Option 的需求——你的类型现在告诉你字符串是否存在。

另一种解决方案是在设置字符串时映射整个类型。在这种情况下,我们消耗整个变量并通过更改生命周期来更改类型:

struct Foo<'a> {
    boo: Option<&'a mut String>,
}

impl<'a> Foo<'a> {
    fn set<'b>(self, boo: &'b mut String) -> Foo<'b> {
        Foo { boo: Some(boo) }
    }

    fn unset(self) -> Foo<'static> {
        Foo { boo: None }
    }
}

fn main() {
    let foo = Foo { boo: None };
    let foo = {
        let mut string = "Hello".to_string();
        let mut foo = foo.set(&mut string);
        foo.boo.as_mut().unwrap().push_str(", I am foo!");
        foo.unset()
    }; // string goes out of scope. foo does not reference string anymore

} // foo goes out of scope

【讨论】:

  • 注意:如果不是微不足道的,最好通过引用传递FooCore
  • 如果 FooCore 被引用为可变的,我无法让它工作。
  • @Cthutu works fine for me。请注意,您没有具体说明“无法使其正常工作”的含义,甚至是什么意思?这意味着我无法指出适当的问答来解释您遇到的问题。即使在 cmets 中,练习有效的沟通技巧也很重要,这在 SO 和现实世界中都很有价值。
  • @Cthutu I did change core to a mutable reference and it worked for me,正如我在之前的评论中所链接的那样。 很难描述它,因为我真的不知道 Rust 在做什么。 — 我不是要你描述任何东西。您所要做的就是提供一个指向带有失败代码的操场的链接。至少,您可以复制并粘贴收到的错误消息。 Rust 有非常好的错误消息。
  • @Cthutu SolvedSolvedSolved。我不明白你通过威胁不推荐 Rust 来达到什么目的。如果 Rust 不适合您的情况或者您无法理解,没关系。并非所有东西都适合每个人——使用你觉得舒服的东西,让你做你需要做的事情。
【解决方案2】:

Shepmaster 的回答是完全正确的:你不能用生命周期来表达这一点,生命周期是一个编译时特性。但是,如果您尝试复制可以在托管语言中运行的内容,则可以使用 reference counting 在运行时强制执行安全性。

安全在通常的 Rust 内存安全意义上。在安全的 Rust 中仍然可能出现恐慌和泄漏;这是有充分理由的,但这是另一个问题的主题。)

这是一个示例 (playground)。 Rc指针不允许突变,所以我只好加了一层RefCell来模仿问题中的代码。

use std::rc::{Rc,Weak};
use std::cell::RefCell;

struct Foo {
    boo: Weak<RefCell<String>>,
}

fn main() {
    let mut foo = Foo { boo: Weak::new() };
    {
        // create a string with a shorter lifetime than foo
        let string = "Hello".to_string();
        // move the string behind an Rc pointer
        let rc1 = Rc::new(RefCell::new(string));
        // weaken the pointer to store it in foo
        foo.boo = Rc::downgrade(&rc1);

        // accessing the string
        let rc2 = foo.boo.upgrade().unwrap();
        assert_eq!("Hello", *rc2.borrow());

        // mutating the string
        let rc3 = foo.boo.upgrade().unwrap();
        rc3.borrow_mut().push_str(", I am foo!");
        assert_eq!("Hello, I am foo!", *rc3.borrow());

    } // rc1, rc2 and rc3 go out of scope and string is automatically dropped.
    // foo.boo now refers to a dropped value and cannot be upgraded anymore.
    assert!(foo.boo.upgrade().is_none());
}

请注意,在string 超出范围之前,我不必重新分配foo.boo,就像在您的示例中一样——当最后一个现存的Rc 指针被删除时,Weak 指针会自动标记为无效。这是 Rust 的类型系统即使在放弃了共享 &amp; 指针的强大编译时保证之后仍然可以帮助您强制执行内存安全的一种方式。

【讨论】:

  • 学究式地,这个仍然不允许你引用生命周期较短的东西^_^。它为您提供了一些信息,可以告诉您该值是否被某些东西保持活动状态,然后您可以从中获得参考。
  • 是的,这是一种技术上更正确(最好的正确方式!)的说法。
猜你喜欢
  • 2018-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-27
相关资源
最近更新 更多