为什么不安全的代码会编译,但推送到向量的类似代码抱怨引用的生存时间不够长



我有类似于以下 Rust 代码的东西,它可以将 Rust 对象存储在某个地方(在实际应用程序中它存储在 Lua 用户数据中)并在以后检索它(从 Lua 调用方法时)。

use std::ptr;
struct Bar(u32);
struct Foo<'a> {
    subobj: &'a Bar,
}
struct State {
    buf: [u8;100],
}
fn stash<T>(state: &mut State, foo: T) {
    let p : *mut T = state.buf.as_ptr() as *mut T;
    unsafe { ptr::write(p, foo); };
}
fn fetch<T>(state: &mut State) -> &mut T {
    let p : *mut T = state.buf.as_ptr() as *mut T;
    unsafe { &mut *p }
}
fn main() {
    let mut state = State{buf: [0;100]};
    // let mut v: Vec<Foo> = Vec::new();
    {
        let bar = Bar(7);
        let foo = Foo { subobj: &bar };
        // v.push(foo); // *does* complain that bar doesn't live long enough
        stash(&mut state, foo);
    }  // bar's lifetime ends here!
    let foo2: &mut Foo = fetch(&mut state); // Boom!
    println!("{}", foo2.subobj.0 + 3);
}

上面的例子显然是错误的,因为它允许我在范围结束后获得对bar的悬而未决的引用。 但是,对于不包含任何引用(或仅包含'static引用)或类似 Rc<T> 的类型,这似乎都很好。

为什么编译,但一个非常相似的程序(而不是推送到向量)抱怨(根据需要)对bar的引用寿命不够长? 我真的不明白Vec::push有什么不同.

我的理解是,类型检查只查看函数签名,而不查看函数体。出于这些目的,unsafe代码不应相关;关键是我试图弄清楚如何将unsafe代码包装到安全接口中。

但是unsafe代码隐藏在函数中 - 我的印象是类型检查在原型处停止,而不是窥视内部 - 而且Vec肯定也有不安全的代码。

您是正确的,类型检查在原型处停止。这里的区别在于,Vec包含您存储在其自己的类型中的类型 - 这是一个Vec<T>

在我深入回答之前,我鼓励你阅读The Rustonomicon,它谈到了Vec是如何实现的,以及如何明智地使用unsafe

若要使代码以与向量相同的方式失败,可以使用 PhantomData 对存储类型进行编码:

use std::marker::PhantomData;
struct State<T> {
    buf: [u8; 100],
    marker: PhantomData<T>
}
fn stash<T>(state: &mut State<T>, foo: T) { ... }
fn fetch<T>(state: &mut State<T>) -> &mut T { ... }

现在,当您在内部块中stash引用时,State的类型被推断为保存引用,并且该引用具有生存期。然后正常的生命周期机制会阻止在块外使用它。

如果您想查看处于工作状态的代码,请注意,在创建State之前还必须移动let bar = Bar(7);

fn main() {
    let bar = Bar(7);
    let mut state = State {
        buf: [0;100],
        marker: PhantomData,
    };
    let foo = Foo { subobj: &bar };
    stash(&mut state, foo);
    let foo2: &mut Foo = fetch(&mut state);
    println!("{}", foo2.subobj.0 + 3);
}

不会说我在这里编写的代码实际上是安全的 - 这需要更多的思考和验证!

最新更新