Type inference issue on instance variables

Why can I define a method like that:

def foo(bar): String
  bar.to_json
end

foo({"x" => 1, "y" => 2})

but that kind of type inference doesn’t work with classes:

class Foo
  def initialize(bar)
    @bar = bar
  end

  def foo: String
    @bar.to_json
  end
end


Foo.new({"x" => 1, "y" => 2}).foo

and it ends up with

Error: can't infer the type of instance variable '@bar' of Foo

What am I missing about Crystal’s type inference and what is the workaround for this?

1 Like

I’m no expert here, but i’d like to have a go:

This defines a function that returns a string. and .to_json returns a string.
But, this does not put any constraints no @bar. anything that has a .to_json could be in @bar.

Does that sound right?

Checkout Type inference - Crystal.

The first example is an example of duck typing. I.e. the compiler figures out all invocations of #foo and knows that each value passed to it responds to .to_json.

However the 2nd example is introducing an instance variable which needs to be typed and known ahead of time. I.e. there’s not really a way the compiler can type this, as the error says because it doesn’t comply with the inference rules.

The solution is to type your ivar. E.g.

  def initialize(@bar : Hash(String, Int32))
  end

To expand on the previous comments: Technically, the compiler could figure out that the call to Foo.new receives a Hash and thus type the instance variable accordingly.
In fact, this used to work in earlier iterations of the language. But it was later changed, because it involved too much magic to be sane (that’s my interpretation at least; I wasn’t around at that time, though).
Now type instance variables require a type restrictions. In many simple cases, the type can be inferred from the variable assignment (for example, when the value is a literal).

For the details behind the change, I recommend The next step · Issue #1824 · crystal-lang/crystal · GitHub

OK, but what if I want to generalize it to mean any type that has to_json method returning a String?

Could you show us some more code and what you are trying to achieve? Talking about these things in isolation sometimes isn’t good. There might be a better way to do what you want.