I recently published a crate that I believe could have a more ergonomic API if there was a direct way to express "this function returns a reference with the lifetime of an object allocated on the caller's stack right before this function was called."
This probably sounds like a strange thing to want. Sometimes you have global data that you would like to return a reference to, but you want to return a more narrow reference than static
. You may know that the data will not actually live until the end of the program, but that it is at least safe to use until the caller finishes. Or possibly you want to return a reference that you can be sure won't be sent across threads.
The way that this occurs in the threadstack library is that I want to return references to thread local data. Thread local data is global but can't have static lifetime because it only lives until the thread finishes. The way the std thread_local!
macro works around this is by making you use the LocalKey::with
method, and making you pass in a closure that the reference will be passed into. From within the closure the reference passed to you from the borrow checkers point of view is only guaranteed to last until the closure finishes. Without static lifetime it can't be given to thread::spawn
.
But the with
closure method is not ergonomic. Your code becomes more nested just to facilitate accessing a variable instead of indicating control flow. If you need to access multiple thread local variables at once your code starts hugging the right edge of the screen.
threadstack works around this by providing a macro, let_ref_thread_stack_value!
that creates a dummy object on the stack, then calls a function passing in a reference to that object, and the function returns a reference to the thread local data but that has the same lifetime as the reference that was passed in to the function. The reference is then bound with let
locally so the user can use it. To me this seems like a big improvement over with
, but it's still not nearly as nice as just being able to have a regular function. The macro is necessary because I need to create an object in the stack frame where the invocation happens in order to get the right lifetime.
I don't have a strong opinion about syntax, but something like 'caller
seems pretty straightforward. Then threadstack's API becomes:
fn get_thread_stack_ref<T>(stack: &ThreadStack<T>) -> &'caller T;