The Crystal Programming Language Forum

Stricter inferrance option?

def do_something(n : Int32?)
  p n
end

if Random.new.rand(10).odd?
  value = 300
end

do_something value

I understand the reasoning, but it just bit me when I (by mistake) did exactly that: assigned under the condition instead of above it, and then passed it on to a nillable arg.

Is there an argument to make for an optional compile mode that either checks the usage before assignment, or even disables nillable assumptions altogether?

Maybe even a warning on any Nils except return types? I can imagine myself preferring explicit sentinels like Type | EmptyValue instead of Type? in some situations.

I mean if you don’t want to pass nil to the method, make the type restriction not nilable?

I understand the reasoning, but it just bit me when I (by mistake) did exactly that: assigned under the condition instead of above it, and then passed it on to a nillable arg

I think this is a good point.

We copied this behavior from Ruby: when you don’t assign to a variable in all branches it just gets to be Nil. This is “good” because you don’t need to write value = nil on the other branch. This is bad because the nil assignment is implicit so it can’t be seen. An immediate consequence of this is that if you try to invoke something on value you will get a compile-error but there’s no code to show where did that nil come from (well, I think the compiler will point to the if but it’s still not clear). The same happens with a case only it’s much worse: with such compiler error you have to check all branches to see where did you make a mistake.

Maybe we can remove this behavior? Then you will get a compile error when you forget to assign a variable in one branch, removing the “nil if not assigned” behavior.

This would make code easier to read and follow at the cost of having to type a bit more. I really don’t mind that.

I wonder how much code will break because of this. Maybe not that much, I don’t know…

1 Like

What about making a new type to represent this? Like Undefined. Would then be able to separate the case where a value is actually nil, or was declared but never assigned a value.

Well, it would be one way to actually implement that. But I don’t know what you would actually do with an Undefined value other than wanting a compile error when it’s created.

If it is assigned in all branches it works as expected. I feel comfortable with no promoting the variables that only appear on one side of the branch.

I see them as more consistent with variables declared inside a block. Although Ruby has do |local; parent| to state the scope of the variables.

Where is value being defined? Wtf
edit: I read @asterite’s post. I understand why now, thank you.

@OP, just do this https://play.crystal-lang.org/#/r/78sk

def do_something(n : Int32)
  p n
end

if Random.new.rand(10).odd?
  value = 300
end

do_something value

Yes, I think I will drop all the nils from my code from now on, however this will not work with the code I don’t own.