Compiling error for Nilable var due to undefined method

Below code gets an Error: undefined method '<<' for Nil (compile-time type is (Array(Node) | Nil)). The @children is initialized on demand when it is nil in method add_child, so the code should work. I expect the @children should be nil when it is a leaf node by default. But it has the compiling error, how to fix this?

class Node
  getter children : Array(self)? = nil

  def initialize(@name : String)
  end

  def add_child(child : self)
    @children = Array(self).new if children.nil?
    @children << child
  end
end

Node.new("root").add_child(Node.new("a child"))

You’re gonna need to chain .not_nil! on all calls to children, because like you said it gets initialized sometime after creating the class instance. You could also look at getter!, as that provides a method that does the non_nil check for you and use that.

.not_nil! is really bad advice. You should avoid that. It’s only meant as a last resort when you can’t write code in a way that the compiler can safely exclude Nil. This is not a use case for .not_nil! because it’s trivial to fix the code for proper type propagation.

Instead, you should assign the instance variable to a local variable and do the nil check on that. That enables the compiler to properly track the types without any explicit nil checking.

children = @children ||= Array(self).new
children << child

The language reference explains the reasons for this.
https://crystal-lang.org/reference/1.2/syntax_and_semantics/if_var.html#limitations

3 Likes

I didn’t know it was bad advice, but depending on the context, I can see how it can cause some issues. But to your point, it might not be the most preferred way and can be avoided.

I like to use the block variant of the getter macro for these cases:

getter children : Array(self) { Array(self).new }

# ...

self.children << child

Essentially does the ||= logic for you under the hood. Granted the one difference is that if you call .children outside of the class you could get an empty array instead of nil. If that’s okay then :+1:.

3 Likes