Stream: t-lang/wg-unsafe-code-guidelines

Topic: Transmuting Locks on Std IO

Lokathor (Jan 05 2020 at 22:07, on Zulip):

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 StdinLock, StdoutLock, and 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?

comex (Jan 05 2020 at 23:10, on Zulip):


Lokathor (Jan 06 2020 at 23:17, on Zulip):

That looks... way too complicated. Seems like a swiss army chainsaw for when you have actual references to things that are supposed to move.

comex (Jan 07 2020 at 04:45, on Zulip):

@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.

comex (Jan 07 2020 at 04:50, on Zulip):

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.

comex (Jan 07 2020 at 04:51, on Zulip):

But it is ugly, because it's trying to shoehorn something into Rust's existing type system that it wasn't designed to do.

comex (Jan 07 2020 at 04:52, on Zulip):

On the other hand, it is safe.

comex (Jan 07 2020 at 04:53, on Zulip):

Your choice whether to go for the safe-but-ugly option or the unsafe-but-slightly-less-ugly one. :)

Lokathor (Jan 07 2020 at 04:54, on Zulip):

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.

Lokathor (Jan 07 2020 at 04:54, on Zulip):

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.

comex (Jan 07 2020 at 04:58, on Zulip):

mm, let me check something

Lokathor (Jan 07 2020 at 04:59, on Zulip):


comex (Jan 07 2020 at 05:04, on Zulip):

yeah, ok, there's no way around the closures :)

comex (Jan 07 2020 at 05:05, on Zulip):

suit yourself then

Lokathor (Jan 07 2020 at 05:05, on Zulip):

For the general case of references literally pointing to other fields, you'd need that

Lokathor (Jan 07 2020 at 05:06, on Zulip):

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

comex (Jan 07 2020 at 05:06, on Zulip):

nah, if you have references literally pointing to other fields, you can't move, period

comex (Jan 07 2020 at 05:06, on Zulip):

whereas rental is for cases where the outer struct can move, because the references points to something behind a heap allocation

comex (Jan 07 2020 at 05:07, on Zulip):

like I said, pretty much your exact use case

comex (Jan 07 2020 at 05:07, on Zulip):

but using unsafe code is fine too :)

Lokathor (Jan 07 2020 at 05:07, on Zulip):

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

comex (Jan 07 2020 at 05:07, on Zulip):

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)

comex (Jan 07 2020 at 05:08, on Zulip):


comex (Jan 07 2020 at 05:09, on Zulip):

comex (Jan 07 2020 at 05:10, on Zulip):

but I think that's not a problem in this case because the reference doesn't point directly within the owning struct

comex (Jan 07 2020 at 05:12, on Zulip):

there's also, but that shouldn't be an issue as long as you don't drop the lock before dropping the struct as a whole

Last update: May 26 2020 at 11:15UTC