Hi there!
I am just trying out crystal language recently. Found the following example does not work:
class X
property :var
def initialize(@var : String | Nil)
end
def some_method
@var = "initialized"
@var.split("") => this throws error during compilation "undefined method 'split' for Nil (compile-time type is (String | Nil))"
end
def some_method
return if @var.nil?
@var.split("") => this throws error during compilation "undefined method 'split' for Nil (compile-time type is (String | Nil))"
end
end
I think the nil check can be improved in both examples above:
There's a section in the Gitbook (albeit a rather hidden one) that covers both of these cases, but somewhat indirectly.
This is because any method call could potentially affect that instance variable, rendering it nil. Another reason is that another thread could change that instance variable after checking the condition.
For the first example:
def some_method
@var = "initialized"
@var.split("")
end
raises an error because of the second part of the quote above. Another thread could potentially change the value of @var
in between the assignment and the method call, so it's type can't be restricted.
The second example:
def some_method
return if @var.nil?
@var.split("")
end
is more tied to the first sentence of the quote, but also affected by the second.
There are a few solutions to this kind of issue, the main one being to assign to a local variable first, where the value _can't_ be affected by anything other than the local scope. For example:
class X
property :var
def initialize(@var : String | Nil)
end
def some_method
@var = "initialized"
# Only call `.split` if `@var` is not nil.
@var.try( &.split(""))
end
# or
def some_method
@var = "initialized"
# Raises an error if `@var` is nil
@var.not_nil!.split("")
end
# or
def some_method
@var = "initialized"
# Local assignment followed by `if` as a suffix
var = @var
var.split("") if var
end
# or
def some_method
if var = @var
# `var` is a local, so it's type can be (and is) restricted inside of the if.
var.split("")
end
end
end
Note that using if var
only restricts the type of the variable _inside_ the condition. It would be nice if return unless var
restricted the type, but I don't think that's universally possible, so it hasn't been implemented.
Thank you, guys!
This is a great learning for me!
It would be nice if return unless var restricted the type, but I don't think that's universally possible, so it hasn't been implemented.
I'm pretty sure it is implemented and restricts the type properly. Of course it can't restrict @fields
, but in the form return unless var = @field
it restricts the var as non-nil for the rest of method.
Most helpful comment
There's a section in the Gitbook (albeit a rather hidden one) that covers both of these cases, but somewhat indirectly.
For the first example:
raises an error because of the second part of the quote above. Another thread could potentially change the value of
@var
in between the assignment and the method call, so it's type can't be restricted.The second example:
is more tied to the first sentence of the quote, but also affected by the second.
There are a few solutions to this kind of issue, the main one being to assign to a local variable first, where the value _can't_ be affected by anything other than the local scope. For example:
Note that using
if var
only restricts the type of the variable _inside_ the condition. It would be nice ifreturn unless var
restricted the type, but I don't think that's universally possible, so it hasn't been implemented.