如何在带有原始指针的结构中使用PhantomData,以使该结构不会超过其他引用结构的生存期?

时间:2018-10-19 23:59:42

标签: rust

我有一个结构,该结构具有不安全的代码和指向另一种结构的原始可变指针。不安全的结构只能在其他结构的生存期中使用,但不能为指针指定生存期。我发现private static async Task Main(string[] args) { await Task.Delay(ms); } 可以用于此未使用的生命周期问题,但是我在使它工作方面遇到问题。我不确定这是否是无效的用例,或者我做错了什么。

Simplified Example

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时出错,但不会出现示例中显示的借位检查错误?

3 个答案:

答案 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_structtest之间共享)。 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>RefCellRwLockMutex。这些都是提供共享(“内部”)可变性的抽象,但是用法不同。阅读文档和下面的链接以获取更多详细信息。

作为最后的选择,如果您需要共享的对对象的可变访问而没有任何开销,并且完全有责任保证自己承担正确性,则可以使用UnsafeCell确实需要使用Test代码,但是您可以编写所需的任何API。请注意,我刚才提到的所有安全抽象都是在内部使用unsafe构建的。没有它,就无法拥有共享的可变性。

链接

答案 1 :(得分:1)

我正在回答自己的问题。我试图解决的问题是使用std::marker::PhantomData通过使用原始指针来实现为结构体增加生命周期,以防止在出现自由错误后使用。您无法使用PhantomData实现此目标。有一个用例来处理未处理的生命周期,但这与我尝试完成的用例不同,这是我困惑/问题的根源。

我已经知道并处理了以下事实:在使用不安全的代码时,必须在出现免费错误和其他错误之后处理使用。我只是以为我可以在编译时(而不是运行时)发生自由错误后处理这种类型的使用。

答案 2 :(得分:0)

我看到该问题的答案已被接受,但我想为该问题添加更多解释。

注意:在使用不安全的代码时,您仍然可以保护自己免受释放后使用错误的影响。这取决于您想要实现的目标。例如,std Vec 是使用不安全代码编写的,但禁止在 Vec 上使用 after free。

以下是问题的详细解释。

例如1

    // 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应该就清楚了。