Immutable object graphs (a.k.a. deep freeze)
Hi there. I know some sort of "#deep_freeze" construct has been proposed many times before. I proposed it in this blog post in 2012: https://tonyarcieri.com/2012-the-year-rubyists-learned-to-stop-worrying-and-love-the-threads
I have also read this issue: https://bugs.ruby-lang.org/issues/2509
This is an area I have thought a lot about as I deal with many people's multithreaded Ruby programs and shared state mutation bugs / data races. I would like Ruby to support an option to fix this problem, and have guaranteed immutable state for entire object graphs rather than individual objects.
The main problem, as I understand it, is existing proposals wish to recurse entire object graphs and deep freeze all their contents. This is particularly problematic with things like Proc.
I propose a simpler #deep_freeze where it is opt-in by individual classes.
It should also be shallow instead of recursive: individual objects should only traverse their local references, determine if they are already #deep_freeze(d), and if not, bail out. No need for recursion.
The basic idea being: to #deep_freeze an object, it can only refer to other #deep_freeze(d)/frozen objects. We do not attempt to walk the entire object graph/aggregate and freeze all the children recursively. Instead merely check that all of the children are already #deep_freeze(d)/frozen, and if they aren't, bail out.
This has many beneficial properties:
- We rely on explicit support from objects to support #deep_freeze. If they don't, bail out. No weird behaviors recursing references and running into crazy Proc object fractals
- We only need an object-local view of the world to determine if we're #deep_freeze-able. The check is little more than walking local object references (i.e. ivars in most cases) and if everything checks out, setting a flag
- We get guarantees that entire object graphs/aggregates are completely frozen all the way down
This requires programmers build up deep frozen object aggregates by starting at the leaves and building more and more complicated aggregates out of deeply frozen pieces.
I think the changes that have gone into Ruby 2.3 are making this easier and more realizable. I would like to continue this trajectory and provide real guarantees for Rubyists about which objects reference object graphs that are truly immutable.
I think immutable state has huge benefits for a lot of areas, most notably concurrency and security.