是否有可能有一个结构,其中包含对结构生命周期较短的值的引用?

时间:2017-08-16 12:15:41

标签: struct rust lifetime

以下是我要存档的简化版本:

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

这显然是完全安全的,因为foo.boo None一旦string超出范围,就会Document document = DocumentHelper.createDocument(); Element documentRoot = null; Element created = null; Node parentNode = null; documentRoot = DocumentHelper.createElement(); parentNode = documentRoot.selectSingleNode("parentXPath"); Namespace ns = new Namespace("name","value"); created = ((Element) parentNode).addElement(new QName("elementName",ns1));

有没有办法告诉编译器?

2 个答案:

答案 0 :(得分:3)

  

这显然是完全安全的

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

在这种情况下,您的原始代码会在启用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

这只是 ,因为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

答案 1 :(得分: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());
}

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