0

Disclaimer: This question is very close to Passing two objects, where one holds a reference to another, into a thread, but not equal.

I have a C library which I call to get a pointer to a struct, and get a *mut ffi_context back. I then wrap that pointer into a rust struct like this:

pub struct MyContext {
  raw_context: *mut ffi_context
}

and implement a suitable Drop method which frees the *mut ffi_context.

Then I can use that context to obtain more structs (*mut ffi_context_dependant), which must not outlive the context. I have consulted the nomicon, and added a PhantomData to my MyContextDependant struct, so rustc shouts at me if I violate the lifetime requirement:

pub struct MyContextDependant<'ctx> {
  raw_context: *mut ffi_context_dependant,
  _phantom: PhantomData<&'ctx MyContext>
}

Now I would like the types to be Send-y. The C library states that one may use the structs from another thread, but not concurrently. That's good because this means I can implement Send for those structs:

  • I can pass &s to anyone (assuming the lifetimes hold) as long as there is no &mut (parallel reading causes no harm)
  • I can pass a &mut to exactly one, if and only if no &s are being held
  • I cannot move a dependant to a thread unless I prove the context lives long enough

However, it forbids me to to move the context and all dependants, which would (in reality) be totally safe as long as I enforce that the move's destination makes the context outlives all dependants.

Is there a sane way to express this in rust? Do I have to write (unsafe) helper functions which deconstruct my rust types, move the raw pointers, and reconstruct the rust types with the appropriate lifetimes?

Benni
  • 1,030
  • 2
  • 11
  • 18
  • Although your question is not identical to the one you link, the `PhantomData` makes it equivalent in practice. And the same solutions apply. The trick of deconstructing the types and passing the raw pointers would not be safe because you can never be sure that there aren't any other dependant objects out there. – rodrigo Oct 29 '20 at 22:20
  • The linked "very close" question is basically the same. The answer there addresses tangible concerns that could ocurr if this was allowed, and even if you're using "phantom" references and not real ones to link the lifetimes, they look the same to the borrow checker. There is no distinction. So yes, you'll either have to unlink and relink your lifetimes, or somehow bundle everything into one object and move that. – kmdreko Oct 29 '20 at 22:20
  • @kmdreko Yes and no: It is very similar, but in the other case there is a memory hazard and in my case there is not: Moving their context **breaks** the references in the dependants, moving my context doesn't break them because there are none. – Benni Oct 29 '20 at 22:53
  • @rodrigo So the language has no decent solution for this? The compiler knows exactly whether other dependants exist (since they take a borrow), I just can't capitalize on that knowledge. – Benni Oct 29 '20 at 22:53
  • You believe the compiler has more information than it actually does. The compiler **doesn't** know the difference between a reference with a lifetime and `PhantomData` because it doesn't keep track of what references *really* do -- that's an intractable problem, when you consider all the things that could happen at runtime. The compiler just uses the lifetimes in the type signature, in order to verify that the constraints of each function signature are upheld, one function at a time. The compiler has no way to know that moving `MyContext` doesn't actually invalidate the dependents. – trent Oct 30 '20 at 01:25
  • @trentcl I have no objections against the compiler not automatically knowing that moving MyContext does not invalidate the dependants, I object to being unable to declare that (with unsafe). So writing an unsafe deconstruct/reconstruct pair (which comes with a little runtime cost) with big warning labels to move **all** dependants or never use them again is the only way to go? – Benni Oct 30 '20 at 10:31

0 Answers0