Some thoughts for Crystal arguments default value and type inference

Following is a example:

require "yaml"

class A
  @option : YAML::Any

  def initialize(option = YAML.parse("{}"))
    @option = option
  end
end

if File.file? "1.yml"
  option = YAML.parse(File.read("1.yml"))
end

a = A.new(option)

This will get error like this:

In 1.cr:15:7

 15 | a = A.new(option)
            ^--
Error: no overload matches 'A.new' with type (YAML::Any | Nil)

Overloads are:
 - A.new(option : ::YAML::Any = YAML.parse("{}"))
Couldn't find overloads for these types:
 - A.new(option : Nil)

Okay, this is expected, because the option args can’t be nil.

So, the question is, what is the purpose of this default value parameter? okay, this make not pass this parameter is possible, But if you pass a nil, it will raise error, Ruby blurs the lines, but, in Crystal, you have to distinguish pass a nil as parameter with not pass it.

Let change code to make pass nil is possible.

require "yaml"

class A
  @option : YAML::Any

  def initialize(option : YAML::Any? = YAML.parse("{}"))
    @option = option
  end
end

if File.file? "1.yml"
  option = YAML.parse(File.read("1.yml"))
end

a = A.new(option)

It should work, right? because @option can never by nil, but still get compile error:

In 1.cr:7:5

 7 | @option = option
     ^------
Error: instance variable '@option' of A must be YAML::Any, not (YAML::Any | Nil)

Any idea?

That’s precisely the problem, @option isn’t nilable, but you’re allowed to pass a nil argument to the constructor. This in turn changes the error from “no overload” to what you see there because you’re trying to assign a variable that can be nilable to an ivar that cannot.

In this case, I’d do something like:

def initialize(option : YAML::Any? = nil)
  @option = option || YAML.parse("{}")
end

Related: [RFC] Undefined Type

def initialize(option : YAML::Any? = nil)
  @option = option || YAML.parse("{}")
end

Yes, a really nice workaround.

@option isn’t nilable, but you’re allowed to pass a nil argument to the constructor. This in turn changes the error from “no overload” to what you see there because you’re trying to assign a variable that can be nilable to an ivar that cannot.

Oops, i messed up something, i thought when pass a nil as parameter, it will use default value YAML.parse("{}"), i am wrong, thanks.