能否创建一个包含指向比结构体寿命更短的值的引用的结构体?

5

以下是我希望实现的简化版:

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,因此这显然是完全安全的。

有没有办法告诉编译器这一点呢?


如果您正在尝试复制在托管语言中可以工作的内容,引用计数和弱引用(非拥有引用)可以让您模仿类似的东西。但是安全性将在运行时执行,而不是由编译器执行。 - trent
在这里提前定义 foo 的目的是什么? - Matthieu M.
@Matthieu M. 还有其他的结构字段,我会在循环中执行此操作,而性能非常重要。在这个例子中它是完全无用的 :D - lncr
@trentcl 尽管在我的情况下这并不相关,因为我实际上之所以不仅创建一个新的结构是出于性能原因,但这仍然似乎很有趣。您介意使用 ´Rc<>` 或弱引用发布答案吗?如果您不这样做,我将稍微研究一下,并在有时间时创建自我回答。 - lncr
2个回答

6

这显然是完全安全的

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

在这种情况下,只要启用了非词法生命周期,您的原始代码就可以编译:

#![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

这是因为foostring超出作用域后将无法使用,而不是因为你将值设置为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

3
注意:如果FooCore不是一些微不足道的东西,通过引用传递可能会更好。 - Matthieu M.
1
@Cthutu 我已将core更改为可变引用,并且它对我起作用了,就像我在之前的评论中提到的那样。很难描述它,因为我真的不知道Rust在做什么。 — 我并不要求你描述任何东西。你所要做的就是提供一个带有失败代码的playground链接。至少,你可以复制并粘贴你得到的错误消息。Rust有非常好的错误消息。 - Shepmaster
抱歉,直到现在我才看到你的消息。我创建了一个示例代码,将Foo分割成FooCore和Foo:https://gist.github.com/rust-play/e68586753b785c02b6c27c02bf90f5b0。问题是如何使FooCore实例可变。我还不确定如何解决这个问题。 - Cthutu
2
@Cthutu 已解决已解决已解决。我不明白你威胁不推荐Rust的目的是什么。如果Rust不适合你的情况或者你不理解,没关系。并不是所有东西都适合每个人——使用让你感到舒适和能够满足需求的工具即可。 - Shepmaster
解决方案是添加大括号?这样重新定义foo就不会破坏原始定义了吗?这是否意味着只有由大括号定义的作用域才定义了销毁点? - Cthutu
显示剩余8条评论

2
Shepmaster的答案完全正确:你无法使用生命周期表达这个概念,因为它只是一个编译时功能。但如果你想复制在托管语言中工作的东西,你可以使用引用计数来在运行时确保安全性。

(“安全性”通常指 Rust 内存安全。在安全的 Rust 中,仍然可能出现 panic 和泄漏;这有很好的理由,但这是另一个问题的主题。)

这里是一个例子(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 类型系统的一种方式,即使删除了共享&指针的强编译时保证,仍然可以帮助您实施内存安全性。

严谨地说,这仍然不允许您引用生命周期较短的内容。它提供了一些可以告诉您该值是否由某些东西保持活动状态的东西,然后允许您获取引用。 - Shepmaster
1
没错,这是更加技术上正确的表达方式(最好的正确方式!)。 - trent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接