So I have an
ncurses style situation. I want to have a struct that represents "the terminal", and it will have exclusive control over the input and output handling while it's around. Naturally, Rust puts us in a thread-safe position for this with the
StderrLock setup. However, each of these locks holds a lifetime back to their handle. So you'd have a struct with three lifetimes to some outer scope the terminal is stuck in. It would be a lot easier if the terminal could just hold all of the stuff at once and then it can just move around without worry. Which brings us to questions about how we could package up the handles with the locks.
(For the purposes of everything I'm about to say, I'm aware that the internals of the standard library are not a stable part of the API. I'm willing to accept that some future version of the standard library might change a detail in here, I just want it to be sound with respect to current Rust.)
So my main thought was that we can just transmute the lock from lock<'a> into lock<'static>, wrap both the handle and the lock into ManuallyDrop, and then have the drop code for this bundle first drop the lock, then drop the handle.
The handles themselves are of the form
Arc<Mutex<BufReader<Maybe<StdinRaw>>>>, and the lock is a MutexGuard,
MutexGuard<'a, BufReader<Maybe<StdinRaw>>>. The ArcMutex itself is kept, lazily initialized, in a static inside the stdin function.
Given what I want to accomplish, does this sound like a sane sort of thing to do? Is there some better way to do it?
That looks... way too complicated. Seems like a swiss army chainsaw for when you have actual references to things that are supposed to move.
@Lokathor shrug In any case, the implementation of rental does the same thing you proposed to do, transmuting lifetimes. It should be fine, honestly even in the face of future changes, unless we somehow give up on the idea that lifetimes are erased at runtime.
But in terms of complexity...
rental is designed for pretty much the exact use case you have. A struct with two fields, where one is an owning pointer (like
Box, or in your case
Arc), and the other is a reference to the data behind that pointer.
But it is ugly, because it's trying to shoehorn something into Rust's existing type system that it wasn't designed to do.
On the other hand, it is safe.
Your choice whether to go for the safe-but-ugly option or the unsafe-but-slightly-less-ugly one. :)
The biggest thing in this case, to me at least, isn't so much safe vs unsafe. It's the ergonomics factors. You don't get any fields any more, you have all sorts of closures to try and use.
So it's very very less ugly to be able to just use the fields directly in all the rest of the type's methods.
mm, let me check something
yeah, ok, there's no way around the closures :)
suit yourself then
For the general case of references literally pointing to other fields, you'd need that
But since it's an Arc Mutex and an Arc MutexGuard (which points to the inner mutex on the heap), neither field in my case actually points to the other
nah, if you have references literally pointing to other fields, you can't move, period
rental is for cases where the outer struct can move, because the references points to something behind a heap allocation
like I said, pretty much your exact use case
but using unsafe code is fine too :)
oh really? yikes I figured it was like... you'd say you'd want &i32 and then it'd store an i32 and then during the closure it'd pass that i32 as well as the reference to wherever it currently was
oh, and regarding whether it's sound: it's significant that Stacked Borrows does not care about lifetimes (although it does care about references versus raw pointers)
but I think that's not a problem in this case because the reference doesn't point directly within the owning struct
there's also https://github.com/rust-lang/unsafe-code-guidelines/issues/125, but that shouldn't be an issue as long as you don't drop the lock before dropping the struct as a whole