Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable instance variables #4359

Open
chocolateboy opened this issue Apr 30, 2017 · 10 comments
Open

Immutable instance variables #4359

chocolateboy opened this issue Apr 30, 2017 · 10 comments

Comments

@chocolateboy
Copy link
Contributor

chocolateboy commented Apr 30, 2017

💡 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

See Also

@ShalokShalom
Copy link

+1

@konovod
Copy link
Contributor

konovod commented Aug 27, 2017

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.

@sevk
Copy link

sevk commented Aug 27, 2017

keep simple , thanks .

@RX14
Copy link
Contributor

RX14 commented Aug 27, 2017

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.

@ShalokShalom
Copy link

@konovod I think 'recommended' is a nice way

@ozra
Copy link
Contributor

ozra commented Aug 29, 2017

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.

@drosehn
Copy link

drosehn commented Aug 29, 2017

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.

@drosehn
Copy link

drosehn commented Aug 29, 2017

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.

@ozra
Copy link
Contributor

ozra commented Aug 30, 2017

@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!

@RX14
Copy link
Contributor

RX14 commented Aug 30, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants