Question about method return type

Hi, I’m learning Crystal by examples, and I came across the following.

Here is the “bad” code.

def find_char(dictionary : Dict, bit_string : BitString) : Char
    a = from_bit_string(bit_string)
    b = Char::ZERO
    dictionary.each do |item|
      return item.char if item.num == a
    end
  end

It should find the char in the Dict, but since it is in the loop, the compiler can not guarantee that happens, so it extends the return type with Nil. Is this supposed to happen, without any warning, that you do not return a value from the function in every case?

After a bit tinkering what’s wrong, came to this conclusion, what would you do in Java, find the value, break from the loop, and return it.

 def find_char(dictionary : Dict, bit_string : BitString) : Char
    a = from_bit_string(bit_string)
    b = Char::ZERO
    dictionary.each do |item|
      b = item.char if item.num == a
      break
    end
    b
  end

Shouldn’t be a warning message for this cases?

Errors don’t show up unless you invoke the method. Methods that aren’t used aren’t type checked. You should be getting an error once you call the method.

Here is the code what I wrote, https://github.com/wahur666/Base64-Demos/blob/master/crystal/src/base64-crystal.cr
The method can be found at line 70.

include MyBase64
dict = create_dict
puts encode(dict, "ASd")

This is the method calling, and with the bad code snippet, you get the following error message

in src/base64-crystal.cr:86: instantiating 'find_char(Array(MyBase64::Entry), Array(Int32))'

 ret_str += find_char(dictionary, item)
                 ^~~~~~~~~

in src/base64-crystal.cr:70: type must be Char, not (Char | Nil)

  def find_char(dictionary : Dict, bit_string : BitString) : Char
      ^~~~~~~~~

As you can see, it stated that the type must be Char, not Char | Nil, but the signature of the method says that I want Char only. I was confused, because how could it be Nil, if I never stated that and there is explicit type restriction. The the compiler recognized the issue, that I do not return value for every possible scenario, but by extending the type union with Nil, just concealed the issue. Is this how it should work?

In your bad code, if the dictionary doesn’t have the item you’re looking for, the loop over dictionary will end, and the end of the method will finally return nil (similar to nothing) to the caller. It’s recommended to return nil to the caller instead of a predefined “error” value like Char::ZERO, so you can handle the error in the caller.

A way to handle the error in the caller to reflect the behavior of your second working method could be:
a_char = find_char(some_dict, bits) || Char::ZERO

2 Likes