The Crystal Programming Language Forum

Nil checks demistifaction

Hello, new guy here. I’m playing with the language a little bit and I’ve decided to implement some data structures. While implementing Linked lists I’ve stomped upon strange behavior. How is that compiler can’t recognize @tail as not nil?

class LinkedList(V)
  getter head : Node(V)?
  getter tail : Node(V)?
  
  ...
  
  # Pushes new node value on the end of the list
  def push(value : V)
    if @head.nil?
      @head = Node.new(value)
      @tail = @head
    elsif !@tail.nil?
      @tail.next = Node.new(value)
      @tail = @tail.next
    end
  end
end

function doesn’t compile and returns

In linked_list.cr:63:13

 63 | @tail.next = Node.new(value)
            ^---
Error: undefined method 'next=' for Nil (compile-time type is (LinkedList::Node(Int32) | Nil))

error.

I’ve eventually fixed it with .not_nil! method but I was wondering how the mechanism behind null reference checks actually works and what are the best practices?

Thanks!

See if var - Crystal.

2 Likes

Oh, that was fast, thank you. So if I understood correctly it’s because of possible mutation of the state from some other thread(fiber)? That means that not_nil! method is the go to way when I don’t expect nil to be present?

Correct, another fiber could mutate that value after the check but before it’s used.

No. As mentioned in the examples you’d want to do like

if h = @head
  # h is not nil
end

or

@head.try do |h|
  # h is not nil 
end

EDIT: not_nil! is best used when you can be 100% sure it won’t be nil, but the compiler isn’t able to prove it, such as some application specific state etc.

2 Likes

Thank you!

It’s also nkot necessary to assign the instance variable inside the if. It’s usually done for brevity but you can do:

head = @head
if head
 ... 
end
2 Likes