The Crystal Programming Language Forum

[Suggestion] Support union types inside nested Hashes

https://crystal-lang.org/reference/syntax_and_semantics/union_types.html

The type of a variable or expression can consist of multiple types.

However,

class Client
  	property buffs : Hash(String, Float32 | Int32)  
  	def initialize
    	@buffs = {"frenzy" => 0}
    end
end

p = Client.new

pp p

Error: instance variable ‘@buffs’ of Client must be Hash(String, Float32 | Int32), not Hash(String, Int32)

class Client
  	property buffs = Hash(String, Int32 | Float32).new
  	def initialize
    	@buffs = {"frenzy" => 0}
    end
end

p = Client.new

Hmm, this works.

What’s the difference between

property buffs = Hash(String, Int32 | Float32).new

and

property buffs : Hash(String, Int32 | Float32)

?

Hmm… how come the code above works, but this doesn’t?

class Zone
  property extra_game_update_hash = Hash(Int64, Hash(String, Int32 | Float32)).new
end

  
class Client
  	property zone_obj : Zone
  	def initialize
    	@zone_obj = Zone.new
    end
end

p = Client.new

p.zone_obj.extra_game_update_hash[5_i64] = {"frenzy" => 0}

Error:

Error: instance variable ‘@value’ of Hash::Entry(Int64, Hash(String, Float32 | Int32)) must be Hash(String, Float32 | Int32), not Hash(String, Int32)

edit: I changed the title to “nested Hashes”. As I think the bug is only when unions are inside a nested Hash? Not entirely sure.

Check https://play.crystal-lang.org/#/r/7sac , the buffs in this example is a union itself.
In the previous snippent, the : Hash(String, Int32 | Float32) restrict the type so no union is made.

Aww, I understand now.

What about

property extra_game_update_hash = Hash(Int64, Hash(String, Int32 | Float32)).new

Not using :, but the compiler still errors out, is that intended?

The type of this expression {"frenzy" => 0} is Hash(String, Int32). These two types:

Hash(String, Int32)
Hash(String, Int32 | Float32)

are not related in any way, these are two different types, no ifs and buts about it.

Look at it this way: you have a generic type Hash(K, V), so when you create a concrete type Hash(k1, v1), it’s not going to be compatible with another Hash(k2, v2), unless k1 == k2, and v1 == v2.

In your case, k1 is String and k2 is also String, however v1 is Int32 | Float32, but v2 is Int32.

Int32 | Float32 and Int32 are two different types. They are not unrelated if used directly, but as soon as you have them inside another type, they lose their relationship and become just two different types.

Hope that helps.

1 Like