简体   繁体   中英

Can I return a struct which uses PhantomData from a trait implementation to add a lifetime to a raw pointer without polluting the interface?

In this question someone commented that you could use PhantomData to add a lifetime bound to a raw pointer inside a struct. I thought I'd try doing this on an existing piece of code I've been working on.

Here's our (minimised) starting point. This compiles ( playground ):

extern crate libc;

use libc::{c_void, free, malloc};

trait Trace {}

struct MyTrace {
    #[allow(dead_code)]
    buf: *mut c_void,
}

impl MyTrace {
    fn new() -> Self {
        Self {
            buf: unsafe { malloc(128) },
        }
    }
}

impl Trace for MyTrace {}

impl Drop for MyTrace {
    fn drop(&mut self) {
        unsafe { free(self.buf) };
    }
}

trait Tracer {
    fn start(&mut self);
    fn stop(&mut self) -> Box<Trace>;
}

struct MyTracer {
    trace: Option<MyTrace>,
}

impl MyTracer {
    fn new() -> Self {
        Self { trace: None }
    }
}

impl Tracer for MyTracer {
    fn start(&mut self) {
        self.trace = Some(MyTrace::new());
        // Pretend the buffer is mutated in C here...
    }

    fn stop(&mut self) -> Box<Trace> {
        Box::new(self.trace.take().unwrap())
    }
}

fn main() {
    let mut tracer = MyTracer::new();
    tracer.start();
    let _trace = tracer.stop();
    println!("Hello, world!");
}

I think that the problem with the above code is that I could in theory move the buf pointer out of a MyTrace and use if after the struct has died. In this case the underlying buffer will have been freed due to the Drop implementation.

By using a PhantomData we can ensure that only references to buf can be obtained, and that the lifetimes of those references are bound to the instances of MyTrace from whence they came.

We can proceed like this ( playground ):

extern crate libc;

use libc::{c_void, free, malloc};
use std::marker::PhantomData;

trait Trace {}

struct MyTrace<'b> {
    #[allow(dead_code)]
    buf: *mut c_void,
    _phantom: PhantomData<&'b c_void>,
}

impl<'b> MyTrace<'b> {
    fn new() -> Self {
        Self {
            buf: unsafe { malloc(128) },
            _phantom: PhantomData,
        }
    }
}

impl<'b> Trace for MyTrace<'b> {}

impl<'b> Drop for MyTrace<'b> {
    fn drop(&mut self) {
        unsafe { free(self.buf) };
    }
}

trait Tracer {
    fn start(&mut self);
    fn stop(&mut self) -> Box<Trace>;
}

struct MyTracer<'b> {
    trace: Option<MyTrace<'b>>,
}

impl<'b> MyTracer<'b> {
    fn new() -> Self {
        Self { trace: None }
    }
}

impl<'b> Tracer for MyTracer<'b> {
    fn start(&mut self) {
        self.trace = Some(MyTrace::new());
        // Pretend the buffer is mutated in C here...
    }

    fn stop(&mut self) -> Box<Trace> {
        Box::new(self.trace.take().unwrap())
    }
}

fn main() {
    let mut tracer = MyTracer::new();
    tracer.start();
    let _trace = tracer.stop();
    println!("Hello, world!");
}

But this will give the error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:53:36
   |
53 |         Box::new(self.trace.take().unwrap())
   |                                    ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'b as defined on the impl at 46:1...
  --> src/main.rs:46:1
   |
46 | impl<'b> Tracer for MyTracer<'b> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: ...so that the types are compatible:
           expected std::option::Option<MyTrace<'_>>
              found std::option::Option<MyTrace<'b>>
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<Trace + 'static>
              found std::boxed::Box<Trace>

I have three sub-questions:

  • Did I understand the motivation for PhantomData in this scenario correctly?
  • Where is 'static coming from in the error message?
  • Can this be made to work without changing the interface of stop ? Specifically, without adding a lifetime to the return type?

I'm going to ignore your direct question because I believe you arrived at it after misunderstanding several initial steps.

I could in theory move the buf pointer out of a MyTrace and use if after the struct has died

Copy the pointer, not move it, but yes.

By using a PhantomData we can ensure that only references to buf can be obtained

This is not true. It is still equally easy to get a copy of the raw pointer and misuse it even when you add a PhantomData .

Did I understand the motivation for PhantomData in this scenario correctly?

No. PhantomData is used when you want to act like you have a value of some type without actually having it. Pretending to have a reference to something is only useful when there is something to have a reference to . There's no such value to reference in your example.

The Rust docs say something about raw pointers and PhantomData , but I perhaps got it wrong

That example actually shows my point well. The Slice type is intended to behave as if it has a reference to the Vec that it's borrowed from:

fn borrow_vec<'a, T>(vec: &'a Vec<T>) -> Slice<'a, T>

Since this Slice type doesn't actually have a reference, it needs a PhantomData to act like it has a reference. Note that the lifetime 'a isn't just made up out of whole cloth — it's related to an existing value (the Vec ). It would cause memory unsafety for the Slice to exist after the Vec has moved, thus it makes sense to include a lifetime of the Vec .

why the commenter in the other question suggested I use PhantomData to improve the type safety of my raw pointer

You can use PhantomData to improve the safety of raw pointers that act as references, but yours doesn't have some existing Rust value to reference. You can also use it for correctness if your pointer owns some value behind the reference, which yours seemingly does. However, since it's a c_void , it's not really useful. You'd usually see it as PhantomData<MyOwnedType> .

Where is 'static coming from in the error message?

Why is adding a lifetime to a trait with the plus operator (Iterator<Item = &Foo> + 'a) needed?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM