Crystal: Unable to initialize variable with static type declaration inline

Created on 2 Jul 2015  路  14Comments  路  Source: crystal-lang/crystal

I don't know that this is a bug, but it seems as though it should be allowed. Currently one is unable to initialize a variable with a static type definition during its declaration.

This works:

foo::UInt16
foo = 1_u16

...but the following results in the error unexpected token: =:

foo::UInt16 = 1_u16

Same story for instance variables as well.

The docs describe the var::Type syntax in two ways... for creating fixed type variables and for creating uninitialized variables.

Allowing an inline initialization would seem useful and safer when the purpose is for a fixed type variable.

accepted draft compiler

Most helpful comment

This is now implemented in master.

Declaring a local variable without an initial value doesn't initialize it to zero. So this is an error:

x : Int32
puts x # Error: read before assignment to local variable 'x'

You can do:

x : Int32 = 0 
puts x # OK

And also:

x : Int32
x = 0
puts x # OK

And using if too, but, as usual, it must be assigned in all branches:

x : Int32
if some_condition
  x = 0
else
  x = 1
end
puts x # OK

So declaring a local variable is always safe.

I think this can sometimes be useful to make an algorithm more readable by adding a type annotation, and also to get nicer error messages.

All 14 comments

The first snippet works. It's not very common to force the type of a variable, so maybe the second syntax is not really needed, but I'll leave this open because I think we thought of this once or twice.

@asterite: Thanks, yeah the first one certainly works, so it's definitely not a pressing issue. If this doesn't make the cut, it sure won't be the end of the world.

I'm still new to Crystal and union types feel pretty foreign so I find myself wanting to fix the type when possible. I imagine I'll warm up to them a bit as I use it.

I've been thinking about adding this issue myself, regarding _instance variables_, glad you did. In certain classes I want members to be uniformly declared, and all typed, so that one doesn't have to think twice about what types the members are when looking at it. And then when default inits are wanted for some fields, I must deviate from the structure otherwise employed.
I'd be _happy_ if it was implemented, but same here, it's not a show stopper.

+1 for this feature

+1

+1 :+1:

Also—I couldn't find if this was brought up already—it would be nice if this were possible:

x :: UInt16
x = 10

and/or

x :: UInt16 = 10

Currently, the compiler gives the following error:

Error in line 2: type must be UInt16, not (Int32 | UInt16)`

This is fixed by simply changing it to 10_u16, which is redundant, given the context. Also, it does look (visually) messy.

Int32 should cover most of the cases. Unsigned integers and the other integer types are in the language for C bindings and doing some optimal stuff when necessary, so having to put a suffix from time to time shouldn't be that bothersome.

Java doesn't have unsigned integers and they do just fine (although they do have integer promotion rules). But once we add that, should this also work:

x :: Complex
x = 10 # ?

I think integer promotions are a big and difficult topic, we might need to think it separately and decide if and how we want to do this, but for now you'll have to be explicit from time to time.

I'd say it should work, 10 would be the Real part of the complex number, and the Imaginary part would stay 0, in your example. Ruby even has a literal for Complex numbers, now that I think of it:

10 + 0i # => (10+0i)

I think integer promotions are a big and difficult topic, we might need to think it separately and decide if and how we want to do this, but for now you'll have to be explicit from time to time.

I don't feel it adds much explicitness, because we already have the declaration x :: UInt16, which should be explicit enough.

However, I don't mind using the suffixes, since—as you pointed out—the need for them is generally uncommon, and when it does arise it's simple enough to use them. It crossed my mind because it would be slightly nicer (as in user-friendly) and look less messy without them in situations when they're not necessary.

If it were written as x = 10_u16—which looks less messy than x :: UInt16 = 10—then it would be necessary, if we wanted x to be UInt16. However, if x was already declared _u16 shouldn't be necessary.

I definitely agree on the typed-var = literal-number should be "reverse order inferred". I think we've discussed this in several issues by know.

And of course the question still standing is "how?". Making it an exceptional case in the compiler and hard coding it for Numeric derivatives would be ok in my world - but not that elegant of course. So being able to define "coercion rules" on a type, perhaps by being able to restrict parameters, not only to type, but also by "literal or not" in from, initialize or some other method by convention could be a more generic way. But then the agreed upon method (say from(n : Literal(Numeric)) - or another notation preferably, since literal is not part of the type) has to be looked up at some reasonable compiler stage (rewrite after typing) and the assign replaced with (pseudo) Assign(the-var, Call(the-found-type, "from", the-literal-value)).

Did this make any sense?

I'm contemplating something along those lines for the "Onyx front-end". The worry is that it will diverge unnecessarily from Crystal with that idea. The type + assign is already in, since it's purely a parsing matter.

This works in Crystal master

class X
  @foo : UInt16 = 1_u16
  @a : String = "Serdar"
end

shall we close this issue @asterite ?

Bump

The original issue talks about local variables, this still isn't implemented, and I don't know if it ever will.

This is now implemented in master.

Declaring a local variable without an initial value doesn't initialize it to zero. So this is an error:

x : Int32
puts x # Error: read before assignment to local variable 'x'

You can do:

x : Int32 = 0 
puts x # OK

And also:

x : Int32
x = 0
puts x # OK

And using if too, but, as usual, it must be assigned in all branches:

x : Int32
if some_condition
  x = 0
else
  x = 1
end
puts x # OK

So declaring a local variable is always safe.

I think this can sometimes be useful to make an algorithm more readable by adding a type annotation, and also to get nicer error messages.

May I say! Such fucking awesomeness!! Woohoo! Hurray! :-D

Was this page helpful?
0 / 5 - 0 ratings