Lifetimes and unique references for avoid deadlock in threadpools

Preface: it's been a long while since I've written a Rust program of any kind, and I haven't really seriously used Rust beyond some short tutorial things. I can usually read it, though, and have studied parts of its semantics several times.


I watched RustConf 2021 - Compile-Time Social Coordination by Zac Burns - YouTube recently, and wanted to experiment with the ideas in that talk. They seemed like a really interesting way to design an API; you can use the Token idea in other languages, but you won't have the compiler to enforce it!

So I ended up creating this short program (corrected link) that I thought would demonstrate the compiler error, forcing the use of drop like in the video. To my surprise, there are no compiler errors! Inserting the drop line doesn't cause or remove any, which leads me to believe it's being done automatically… but as expected, inserting a use of connection after the second mutable borrow is a compiler error.

My question is twofold:

  1. Does the compiler automatically drop(conn) in this example? (Is there a way to know that, say, by expanding the program with all the compiler-inserted pieces?)
  2. Is there a way to get this sample program to trigger the compiler error discussed in the video, so that I have to manually insert drop(conn)? (This wouldn't necessarily be ideal for day-to-day use, but I would like to see it actually happen once :slight_smile:.)

There is no conn in your example, so I can't tell what you are referring to. However, in general, everything is dropped automatically at the end of its containing scope. (There are mechanisms to prevent this, although those are explicit, and the default is to always drop automatically.)

Bah, somehow it didn't capture the edited code. Fixing… Rust Playground

I don't know when you last used Rust, but lifetimes are no longer lexically scoped. This means that many types can have a lifetime that ends much shorter than before -- for example, your conn has a lifetime that ends before the emit_event. I'm purposefully avoiding the word "dropped" here, but it is unusable after that point. As such, emit_event can be called with a fresh unique borrow of t.

One way to know that is what has made your program compileable is to add a usage and see if it causes any problems, like your println!... though NLL is smart enough it can be tricky in more complicated situations.

However, types that implement Drop still run Drop::drop at the end of their lexical scope, if there's nothing that consumes them beforehand (and they're not returned out of the scope). This call to drop constitutes a use of the variable. Thus...

...if you want to see it in action, you can implement Drop for Connection<'_>.

Playground.

1 Like

Aha! I forgot that the video did mention that connections should be returned to the pool, which was handled by the Drop mechanism. This makes your solution rather obvious in hindsight :slight_smile: thanks for pointing this out.

I'm still curious if there's a way to see the lifetimes, now, but I no longer need it for this example.

The NLL RFC has some ascii art for various cases, but I'm not aware of any lifetime visualizer generally. It can be tricky to map in the case of loops with interior branches, etc etc. For the linear case though, I believe "as short as possible" is a decent approximation.