为什么不安全的代码可以编译,但推送到向量的类似代码会抱怨引用的寿命不够长?
Why does unsafe code compile, but similar code that pushes to a vector complains that a reference doesn't live long enough?
我有类似下面的 Rust 代码,它可以将 Rust 对象存储在某处(在实际应用程序中它存储在 Lua 用户数据中)并稍后检索它(当从 [=26= 调用方法时) ]).
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
的作用域结束后获得对 bar
的悬空引用。但是,对于不包含任何引用(或仅包含 'static
个引用)或类似 Rc<T>
.
的任何类型,它看起来都不错
为什么可以编译,但一个非常相似的程序(改为推送到向量)抱怨(根据需要)对 bar
的引用不够长?我真的不明白 Vec::push
.
有什么不同
我的理解是类型检查只看函数签名而不看函数体。出于这些目的,unsafe
代码不应该是相关的;关键是我想弄清楚如何将 unsafe
代码包装到安全接口中。
But the unsafe
code is hidden inside functions - I was under the impression that type checking stopped at the prototype, rather than peeking inside - and Vec
surely has unsafe code under the hood too.
类型检查止于原型是正确的。这里的区别在于 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);
}
我不会说我在这里编写的代码实际上是安全的——这需要更多的思考和验证!
我有类似下面的 Rust 代码,它可以将 Rust 对象存储在某处(在实际应用程序中它存储在 Lua 用户数据中)并稍后检索它(当从 [=26= 调用方法时) ]).
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
的作用域结束后获得对 bar
的悬空引用。但是,对于不包含任何引用(或仅包含 'static
个引用)或类似 Rc<T>
.
为什么可以编译,但一个非常相似的程序(改为推送到向量)抱怨(根据需要)对 bar
的引用不够长?我真的不明白 Vec::push
.
我的理解是类型检查只看函数签名而不看函数体。出于这些目的,unsafe
代码不应该是相关的;关键是我想弄清楚如何将 unsafe
代码包装到安全接口中。
But the
unsafe
code is hidden inside functions - I was under the impression that type checking stopped at the prototype, rather than peeking inside - andVec
surely has unsafe code under the hood too.
类型检查止于原型是正确的。这里的区别在于 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);
}
我不会说我在这里编写的代码实际上是安全的——这需要更多的思考和验证!