Search code examples
crystal-lang

Why doesn't crystal's type inference work on classes as expected?


Why can I define a method like that in Crystal:

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?


Solution

  • The equivalent class based approach is making the class a generic:

    require "json"
    
    class Foo(T)
      def initialize(@bar : T)
      end
    
      def foo
        @bar.to_json
      end
    end
    
    
    puts Foo.new({"x" => 1, "y" => 2}).foo
    

    Instance variables need their type set in one way or another because lexicographical type flow analysis is much harder and thus slower to do for them. Also classes build the base of your program so typing them as narrow as possible not only makes the compiler's job easier, it also makes them easier to use. Too open type restrictions on instance variables can lead to quite long and confusing error messages.

    You can read more at the original proposal introducing the change to require type annotations on instance variables: https://github.com/crystal-lang/crystal/issues/2390