From: "jeremyevans0 (Jeremy Evans)" Date: 2021-09-22T22:32:59+00:00 Subject: [ruby-core:105386] [Ruby master Feature#18035] Introduce general model/semantic for immutable by default. Issue #18035 has been updated by jeremyevans0 (Jeremy Evans). tenderlovemaking (Aaron Patterson) wrote in #note-14: > Ah right. I think your example is missing a `freeze`, but I get it. If freezing an instance were to freeze all ancestors of the singleton, wouldn't that extend to `Object` / `BasicObject`? I feel like we'd have to stop freezing *somewhere* because it would be pretty surprising if you can't define a new class or something because someone did `[].freeze`. Simple statements like `FOO = [1].freeze` wouldn't work (as Object would get frozen before we could set the constant). Correct. `Kernel#freeze` behavior should not change anyway, it should continue to mean a shallow freeze. This does point out that a `#deep_freeze` method on an object doesn't result in true immutability. You would have to pick an arbitrary point in the class hierarchy unless you wanted it to freeze all classes. I don't like such an approach. > Maybe we could figure out a cheap way to copy things so that a mutation to the `Class.new` from your example wouldn't impact the instance `a`. Copying method handles into a singleton is one simple idea, but I cannot see how that would work with `super`, and it would result in a significant performance decrease. > But regardless it seems like gradual introduction would be less surprising. IOW maybe the goal would be to make *all* references immutable, but that really isn't practical. Instead expand the frozen horizon as much as we can without breaking existing code? I don't think we should change the semantics of `Kernel#freeze`. In regards to an `Immutable` module, I'm neither opposed to it nor in favor of it, but we should recorgnize that it would not be able to offer true immutability. ioquatix (Samuel Williams) wrote in #note-15: > I would like us to define a model for immutability that has real world use cases and applicability - i.e. useful to developers in actual situations rather than theoretically sound and impossible to implement or impossible to use. Not that I'm saying theoretically sound is not important, just that we have to, as was said above, stop somewhere. Since this proposal includes a new module, it's totally optional. But that module is semantically independent from how we actually implement some kind of `deep_freeze`. The point of the module is to enforce it in a visible way - as in, this class will always be frozen. Such a design can then be used by the interpreter for constant propagation, etc. I don't think Ruby necessarily has an immutability problem currently. You can override `#freeze` as needed in your classes to implement whatever frozen support you want, and freeze objects inside `#initialize` to have all instances be frozen (modulo directly calling `allocate`). I have a lot of experience developing libraries that are designed to be frozen after application initialization. Both Sequel and Roda use this approach, either by default or as an option, and freezing results in significant performance improvements in both. I don't believe Ruby's current support for freezing objects is lacking, but I recognize that it could be made easier for users. If you want to freeze the entire core class hierarchy, you can, and if you do it correctly, nothing breaks. I know this from experience as I run my production web applications with this approach (using https://github.com/jeremyevans/ruby-refrigerator). However, with this approach, the class/module freezing is not implicit due to instance freezing, it's explicit after an application is fully initialized, before accepting requests. The reason to do this is to ensure that nothing in your application is modifying the core classes at runtime. ---------------------------------------- Feature #18035: Introduce general model/semantic for immutable by default. https://bugs.ruby-lang.org/issues/18035#change-93800 * Author: ioquatix (Samuel Williams) * Status: Open * Priority: Normal ---------------------------------------- It would be good to establish some rules around mutability, immutability, frozen, and deep frozen in Ruby. I see time and time again, incorrect assumptions about how this works in production code. Constants that aren't really constant, people using `#freeze` incorrectly, etc. I don't have any particular preference but: - We should establish consistent patterns where possible, e.g. - Objects created by `new` are mutable. - Objects created by literal are immutable. We have problems with how `freeze` works on composite data types, e.g. `Hash#freeze` does not impact children keys/values, same for Array. Do we need to introduce `freeze(true)` or `#deep_freeze` or some other method? Because of this, frozen does not necessarily correspond to immutable. This is an issue which causes real world problems. I also propose to codify this where possible, in terms of "this class of object is immutable" should be enforced by the language/runtime, e.g. ```ruby module Immutable def new(...) super.freeze end end class MyImmutableObject extend Immutable def initialize(x) @x = x end def freeze return self if frozen? @x.freeze super end end o = MyImmutableObject.new([1, 2, 3]) puts o.frozen? ``` Finally, this area has an impact to thread and fiber safe programming, so it is becoming more relevant and I believe that the current approach which is rather adhoc is insufficient. I know that it's non-trivial to retrofit existing code, but maybe it can be done via magic comment, etc, which we already did for frozen string literals. -- https://bugs.ruby-lang.org/ Unsubscribe: