Search code examples
crystal-lang

Why Crystal fails to infer type for instance variable?


Why this code fails for the instance variable?

a = 4.days # Works
class A
  @a = 4.days # Fails
end

P.S.

Are there any plans to improve it in the future? Seems to be very common and useful thing.


Solution

  • Starting with Crystal 1.4, this is now supported. It is legal code.

    The topic came up again recently, and now the compiler is smart enough to detect most of the simple cases (thanks to Asterite's changes in PR #11812!). Now the following examples, which used to be illegal, are now supported:

    class A
      @x = 4.days
    end
    
    class B
      @x = 2 * 3 + 4
    end
    
    class C
      @x = [1, 2, 3].reverse  # (will be Array(Int32))
    end
    

    Update 6th April 2022: Fortunately, my previous answer is mostly outdated now for Crystal 1.4. I'm leaving it in for historic reasons; parts of it are still relevant, but the simple cases like in the original question are legal Crystal now. Only in older versions (up till 1.3.2), it will fail to deduce 4.days or 1 + 1.

    @a = 4 would work as 4 is a literal, but 4.days is an expression and thus cannot be automatically deduced. For the same reason @a = 1 + 1 also requires explicit types, even though @a = 2 would not.

    Regarding future plans, interestingly that feature existed, but was intentionally removed.

    In short, the reason is to keep the compilation step simple. Automatically inducing the types is elegant, but comes with drawbacks:

    • Slower compilation
    • Generating good error messages for ill-typed programs becomes difficult
    • Reusing previous compilation's result becomes difficult (or impossible?)

    The reason is that, in general, you have to analyze chains of function calls throughout the whole source code. Note that not all expressions are as simple as 1 + 1. Even 4.days is already somewhat complex, as the compiler needs to deduce the return type of the days method call.

    If you want to learn more about the design decision, I recommend reading the discussion from 2015.

    You may ask, why does it work for the first assignment outside of the class? I assume that in that case the context is more local. Thus, the arguments against allowing it do not apply in full. For instance, if you change a statement inside a function, its effects are not as global as changing the layout of a class.

    Again, it is a trade-off. But forcing types in both cases would change the experience drastically. While the effect is rather small in the context of the class, forcing explicit types for any assignment would turn the "Ruby-like" Crystal language into a traditional typed language, where each assignment needs to be explicitly typed.