我有一个结构,该结构具有不安全的代码和指向另一种结构的原始可变指针。不安全的结构只能在其他结构的生存期中使用,但不能为指针指定生存期。我发现private static async Task Main(string[] args)
{
await Task.Delay(ms);
}
可以用于此未使用的生命周期问题,但是我在使它工作方面遇到问题。我不确定这是否是无效的用例,或者我做错了什么。
std::marker::PhantomData
如果我直接创建use std::marker::PhantomData;
pub struct Test {
value: u32,
}
impl Test {
pub fn value(&self) {
println!("{}", self.value)
}
pub fn set_value(&mut self, value: u32) {
self.value = value;
}
}
// I want compiler to complain about the lifetime of test
// so that UnsafeStruct is not used after test is dropped
pub struct UnsafeStruct<'a> {
test: *mut Test,
phantom: PhantomData<&'a mut Test>,
}
impl<'a> UnsafeStruct<'a> {
pub fn new(test: &'a mut Test) -> UnsafeStruct<'a> {
UnsafeStruct {
test: test,
phantom: PhantomData,
}
}
pub fn test_value(&self) {
unsafe { println!("{}", (*self.test).value) }
}
pub fn set_test_value(&mut self, value: u32) {
unsafe {
(*self.test).set_value(value);
}
}
}
fn main() {
// No borrow checker errors
// but the compiler does not complain about lifetime of test
let mut unsafe_struct: UnsafeStruct;
{
let mut test = Test { value: 0 };
unsafe_struct = UnsafeStruct {
test: &mut test,
phantom: PhantomData,
};
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
unsafe_struct.set_test_value(3);
unsafe_struct.test_value();
// Lifetime errors caught
// but there will be borrow checker errors if you fix
let mut unsafe_struct: UnsafeStruct;
{
let mut test = Test { value: 0 };
unsafe_struct = UnsafeStruct::new(&mut test);
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
unsafe_struct.set_test_value(3);
unsafe_struct.test_value();
// Borrow checker errors when you fix lifetime error
{
let mut test = Test { value: 0 };
let mut unsafe_struct: UnsafeStruct;
unsafe_struct = UnsafeStruct::new(&mut test);
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
}
,则编译器不会捕获生存期错误,并且无论如何我都想使用构造函数。如果我使用构造函数,则会出现借阅检查器错误。是否可以修复此代码,以使编译器在尝试在相应UnsafeStruct
的生存期之外使用UnsafeStruct
时出错,但不会出现示例中显示的借位检查错误?
答案 0 :(得分:1)
TL; DR 您所做的违反了可变引用的排他性要求,但是您可以使用共享引用和内部可变性来使API正常工作。
&mut T
引用表示对T
的专有访问。当您使用&mut
借用一个对象时,在&mut
借用的整个生命期内,不得通过任何其他引用访问该对象(可变或不可变)。在此示例中:
let mut test = Test { value: 0 };
let mut unsafe_struct: UnsafeStruct;
unsafe_struct = UnsafeStruct::new(&mut test);
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
unsafe_struct
使&mut
的{{1}}借项保持有效。内部包含一个原始指针并不重要。它可能不包含任何内容。 test
中的'a
延长了借用期限,使直接访问UnsafeStruct<'a>
的行为不确定,直到最后一次使用test
为止。
该示例建议您实际上希望共享访问资源(即在unsafe_struct
和test
之间共享)。 Rust具有共享的引用类型。是unsafe_struct
。如果您希望在借用期间仍可以访问原始的&T
,则该借用必须可以共享(T
),而不是独占(&
)
如果您拥有的都是共享参考,您该如何进行某些变异?使用内部可变性。
&mut
没有use std::cell::Cell;
pub struct Test {
value: Cell<u32>,
}
impl Test {
pub fn value(&self) {
println!("{}", self.value.get())
}
pub fn set_value(&self, value: u32) {
self.value.set(value);
}
}
pub struct SafeStruct<'a> {
test: &'a Test,
}
impl<'a> SafeStruct<'a> {
pub fn new(test: &'a Test) -> SafeStruct<'a> {
SafeStruct { test }
}
pub fn test_value(&self) {
println!("{}", self.test.value.get())
}
pub fn set_test_value(&self, value: u32) {
self.test.set_value(value);
}
}
代码了-Cell
是安全的抽象。为了线程安全,或者如果unsafe
的实际内容比较复杂,也可以使用AtomicU32
而不是Cell<u32>
,RefCell
,RwLock
或Mutex
。这些都是提供共享(“内部”)可变性的抽象,但是用法不同。阅读文档和下面的链接以获取更多详细信息。
作为最后的选择,如果您需要共享的对对象的可变访问而没有任何开销,并且完全有责任保证自己承担正确性,则可以使用UnsafeCell
。 确实需要使用Test
代码,但是您可以编写所需的任何API。请注意,我刚才提到的所有安全抽象都是在内部使用unsafe
构建的。没有它,就无法拥有共享的可变性。
答案 1 :(得分:1)
我正在回答自己的问题。我试图解决的问题是使用std::marker::PhantomData
通过使用原始指针来实现为结构体增加生命周期,以防止在出现自由错误后使用。您无法使用PhantomData
实现此目标。有一个用例来处理未处理的生命周期,但这与我尝试完成的用例不同,这是我困惑/问题的根源。
我已经知道并处理了以下事实:在使用不安全的代码时,必须在出现免费错误和其他错误之后处理使用。我只是以为我可以在编译时(而不是运行时)发生自由错误后处理这种类型的使用。
答案 2 :(得分:0)
我看到该问题的答案已被接受,但我想为该问题添加更多解释。
注意:在使用不安全的代码时,您仍然可以保护自己免受释放后使用错误的影响。这取决于您想要实现的目标。例如,std Vec
是使用不安全代码编写的,但禁止在 Vec
上使用 after free。
以下是问题的详细解释。
// No borrow checker errors
// but the compiler does not complain about lifetime of test
let mut unsafe_struct: UnsafeStruct;
{
let mut test = Test { value: 0 };
unsafe_struct = UnsafeStruct {
test: &mut test,
phantom: PhantomData,
};
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
unsafe_struct.set_test_value(3); // line uaf
unsafe_struct.test_value(); //line uaf
我假设您在问为什么 rust 编译器接受标有 line uaf
的行。答案是您直接操作指针。 Rust 生命周期只能参考。
Lifetime errors caught
but there will be borrow checker errors if you fix
let mut unsafe_struct: UnsafeStruct;
{
let mut test = Test { value: 0 };
unsafe_struct = UnsafeStruct::new(&mut test);
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
unsafe_struct.set_test_value(3);
unsafe_struct.test_value();
借用检查器错误很容易理解,就像在 Rust 中一样,您不能同时拥有多个可变引用或可变引用和不可变引用。
对于生命周期错误,这是因为当您使用 UnsafeStruct
创建 ::new
时,您正在使用该函数
pub fn new(test: &'a mut Test) -> UnsafeStruct<'a>
。该函数断言输入引用 test
将在结构的生命周期 'a
内有效。
但是在上面的代码(示例 2)中,new
函数的输入生命周期比结构体的生命周期短。让我对它们进行如下注释
let mut unsafe_struct: UnsafeStruct;
{
let mut test = Test { value: 0 };
unsafe_struct = UnsafeStruct::new(&'test mut test); // the reference of test has the lifetime 'test which is clear that it is shorter than the lifetime 'struct.
unsafe_struct.set_test_value(1);
test.value();
test.set_value(2);
unsafe_struct.test_value();
}
unsafe_struct.set_test_value(3);
unsafe_struct.test_value(); // the lifetime of unsafe_struct ends here.
有了上面的解释,例子3应该就清楚了。