Union types in runtime

Can anyone explain how the compiler generates the code that will work for union types in runtime. For example, I have this crystal code:

def myfunc : Int32 | String
  if Time.utc.to_unix % 2 == 0
    return 1
    return "a"

x : (Int32 | String) = myfunc
puts x * 2

I have a function that can return either int or string. And I do not know what type I will have until runtime. But I have "*" function that has different behaviour for different types. For int it just doubles the number (we get 2), but for string it concatenates the string (we get "aa"). How do we know in runtime what actually should we do with the value in x since we do not have types in runtime?


  • The union type value has a type id embedded which describes at runtime which type it is (this information is available as an undocumented method Object#crystal_type_id).

    The implementation of the union types' #* method then uses multi dispatch to call the respective runtime types' implementation. It can be thought of as somethign like this:

    fun "(Int32 | String)#*"(*args)
      case crystal_type_id
      when String.crystal_instance_type_id
      when Int32.crystal_instance_type_id