Wondering about interior mutability in old rustc

I've been working on a compiler of my own of some sorts, and lately I've run into a number of roadblocks that have made it difficult to move forward. One thing I've found particularly useful lately is looking at the design of rustc; I've been reading a lot of the online documentation and the rustc guide and borrowing some of the ideas therein.

In particular this past week I've been running into some frustrations with how to carry around all of the important things that much of my compiler needs access to (the outputs of name resolution, type information, options/config, a diagnostic printer...). Looking at rustc_middle::ty::TyCtxt today doesn't seem all that useful to me because this area of the compiler seems to have been heavily impacted by incremental compilation and parallelization (non-goals of my compiler).

So let's take a look back to simpler times, in 2015. In this version, I encounter something I find a bit surprising:

Nearly every field on this type back then was wrapped in a RefCell!

Now, I've made the occasional sparing use of RefCell here and there in some projects, but it's always the exception and not the norm. Those weird cases where I need shared borrows for some reason but don't care about multithreading, and where I can easily guarantee that all of the borrows are "short enough" that they'll never overlap. I'm not sure if I have ever written a pub RefCell field.

So this really leaves me wondering:

  • What advantages might have been offered by having all of these RefCells as opposed to bare fields?
  • pub RefCell fields feel dangerously open to me, as conflicts could arise between distant pieces of code. Was there/is there a set of conventions to help avoid these scenarios? (e.g. "a method on a struct should not borrow any of the struct's own pub RefCells, and a function that borrows a RefCell should avoid passing its owner around into other functions")
1 Like

Note that almost all those refcells have been removed in 2021: rustc_middle::ty::context::GlobalCtxt - Rust. If I had to guess, I'd say the reason they were there originally is so the compiler could pass out lots and lots of references to the global context, which is only possible if the reference is immutable.

A better example in 2021 is rustc_infer::infer::InferCtxt - Rust - I asked a while back why it has interior mutability and never got an answer, so the answer is probably "it was easier to write that way and no one got around to fixing it". I tried removing the interior mutability a while ago and ran into lots of issues, I still have an abandoned branch somewhere.

  • pub RefCell fields feel dangerously open to me, as conflicts could arise between distant pieces of code. Was there/is there a set of conventions to help avoid these scenarios? (e.g. "a method on a struct should not borrow any of the struct's own pub RefCell s, and a function that borrows a RefCell should avoid passing its owner around into other functions")

In my experience, not really. The rustc API is not really planned and most issues you only hit once test cases start failing unfortunately.

1 Like