:bulb: Immutable objects are useful because they are inherently thread-safe. Other benefits are that they are simpler to understand and reason about. -- Immutable object
If you want to narrow the type of an instance variable in Crystal you have to assign it to a local variable first, as documented here. This is a kludge which continues to trip users up:
If instance variables could be declared immutable, we wouldn't have to launder them through local variables because they would be guaranteed not to change.
Since we can't introduce variable declarators (presumably), how about a new operator for immutable initialization:
@foo := "bar"
In addition, auto-initialization could be immutable by default e.g.:
Before:
def initialize(@foo : Int32, @bar = 42)
end
def increment_foo
@foo += 1 # OK
end
def increment_bar
@bar += 1 # OK
end
After:
def initialize(foo : Int32, @bar = 42)
@foo = foo
end
def increment_foo
@foo += 1 # OK
end
def increment_bar
@bar += 1 # Error
end
+1
I don't know about "being default" part (I'll like it but it's a breaking change and can cause confusion. How to autoinitialize mutable fields then?) but there is a lot of cases where some field is assigned only at creation.
keep simple , thanks .
How did I miss this issue!
It would be possible, I think, for the compiler to detect if an instance variable is immutable or not without changing anything. I'm not sure if it would be a good idea though. Having which ivars are immutable (I'd prefer to call this readonly, as for classes / pointers the contents of the class can change) documented would be great.
@konovod I think 'recommended' is a nice way
Yeah, set-once ivars is a common pattern, and formally saying so gives both clarification to human reader and low-level optimization benefits to backend. +1.
Are we talking about immutable variables, or variables which have immutable types once set? The page at docs/if_var is talking about how the type might change within an if statement.
I ask because it seems to me that both kinds of "immutable" could be valuable.
Hmm. "immutable type" is not a good description of what I'm thinking of. I have instance variables which I initialize as @somevar = nil, but after the initialization I would never re-set the value to nil. So once the variable is given a non-nil value, it will never become nil again. In fact, it'd be a bug in my own logic if anything did set it to nil.
Not sure if the compiler should have explicit support for that, but the idea matches the issue discussed in docs/if-var.
@drosehn - this is immutability regarding value, or well, _not even that_; just variable/symbol (the value can still be mutated [depending on our definition of value in this context], but you can't reassign the variable), that's what I was referring to at least.
Regarding the type inference, to stick to a first inferred type, and consider subsequent variations errors, that's another thing. Normally would solve that by actually typing the variable (which wasn't possible for locals in earlier releases). Others and I have suggested a way to mark for locking the inference to first inferred type, disallowing type unions, thereby giving the result you seek. Haven't looked lately on the discussion of those issues.
With regard to a late value initialization that is still immutable in practice, that's also something that has been pondered in a couple of issues. For me it was the (_seemingly straight forward_) case of being able to have methods doing inits of some ivars, invoked from one or more instance initializers (the "constructors"), and there are other situations too that are even "late for real" (_still undefined post object instantiation_), like many come across in android dev (not related to crystal, but for example). Unfortunately compilation cost is increased a bunch by doing reachability analysis to ensure that a variable, despite being _uninitialized_, is never accessed before it is initialized, even though at an arbitrarily later stage. Concurrency complicates it further. _I here differentiate between "being initially nil" (which _is_ a value, of the Nil type) and "uninitialized" meaning it is still T instead of T|Nil, but is set and subsequently used after it's containing instance life cycle has already begun_. It would be great out of a user perspective! Unfortunately I think the only way to circumvent it practically is by unsafe practices like "parking" the ivar on a dummy value before real init to avoid the Nil union - in which case the insurance of knowing it is never used before it's set (_for real, not work-around-dummy-set_) is thrown out of the window, along with insurance that it is not re-set.
_Disclaimer: Sorry if I've completely misunderstood something and clutter up the comment space here!_
I would have it so that the value can only be written to inside the constructor (before self escapes), and after the constructor is finished the value is fixed. This also means the type is fixed for all methods. The class cannot be reentrant before self escapes, so types can be checked without if foo = @foo, and after that the variable cannot be set so the same holds. So theoretically this could work.
However I think changing the semantics so subtly there could be more confusing than helpful, and using if foo = @foo is quite succinct. I don't mind the idea of immutable instance variables but changing semantics could be a bit weird.
Most helpful comment
I don't know about "being default" part (I'll like it but it's a breaking change and can cause confusion. How to autoinitialize mutable fields then?) but there is a lot of cases where some field is assigned only at creation.