Pushing the usage of TLS for TyCtxt

I’ve been recently wondering about how the idea of making TyCtxt a global would be like. Some notable benefits of using a thread local are:

  • Less syntax noise. This eliminates the tcx parameter that is put on almost every function.
  • Availability of TyCtxt inside library trait impls. Useful cases include formatting traits (Display, Debug), serialization traits, and finally Drop.

In the following of this post, I will discuss the limitations of scoped thread local (which is already implemented as ImplicitCtxt but rarely used), followed with a new proposal of thread local handle and its improvements and drawbacks.

Limitations of scoped thread local

  • Whether a variable is initialized is check at runtime, adding overhead and possibilty of panics for each access.
  • Usage of closures to access globals is extremely verbose. Because thread locals are not exactly 'static, they must be accessed with the LocalKey::with method.

A new approach: Thread local handle

Instead of hiding the context as a static import, you would use a ZST handle with lifetime to hold the fact that the thread local is initialized. This idea is based on that most API designs has a struct store either a Rc or reference to “parent” context, and when we want to reduce the overhead per object thread-local storage becomes a good fit.


  • Initialization is ensured at compile time
  • You pass around a lifetime-annotated struct so you don’t need the noisy closures to access TLS
  • Maybe more clear that a context exist


  • This approach doesn’t work across thread. Any struct that includes the context is effectively fixed to the thread it was created. (A possible approach is to use some auto-trait juggling to make a safe API, another is just use unsafe + guideline to workaround)
  • As the handle doesn’t contain any data, passing it around could also be seen as unnecessary syntax overhead (although still less than passing as function argument)

The primary motivation of this proposal, as described at the beginning, is to reduce syntax overhead as well as enabling use of the context in traits. Whatever approach we’re going to use, I believe this would improve the ergonomics of working with the compiler. How do you think?


I don’t see how passing around a ZST handle instead of a TyCtxt is better. Can you elaborate on that point?

I guess we’d be able to get rid of the 'a lifetime that TyCtxt needs. And we’d have one less pointer per datastructure that contains a TyCtxt.

The benefits you listed are only a benefit over the LocalKey::with method, not over the current approach. How would this improve Display and Debug impls? Would every type that has a 'tcx lifetime contain the ZST?

The disadvantage of not being able to send objects across thread boundaries is also a big problem (cc @Zoxc). Might be resolvable by having some sort of transfer wrapper that will initialize the thread local when unwrapping the inner value, but it’s certainly additional complexity.


There are a lot of places where TyCtxt is passed as a parameter, not a member of &self. A ZST doesn't have space overhead when stored in a struct, so it can be stored everywhere including those structs with frequent instantiations.

Oh, and by the way it is a ZST handle with lifetime so you won't get rid of the 'a anyway.

Likely yes, as described above.

Access to all information, mainly name resolution information can be gained. Although, it seems to be already done where necessary, like for DefId.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.