Crystal: Union types don't respect assignment operator inside nested Hashes on creation

Created on 12 Oct 2019  路  5Comments  路  Source: crystal-lang/crystal

Reduced example that shows a Hash working with a Union:

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

p = Client.new
p.buffs["frenzy"] = 0
p.buffs["frenzy"] = 0f32

pp p

This works just as the developer would expect! Great.

However, when you have a nested Hash that has a union, and you need to create and assign them, the compiler won't let you. For example:

Use case:

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

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

p = Client.new
p.in_zone_obj.extra_game_update_hash[5_i64] = {"frenzy" => 0}

pp p

This will give an error:

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

Thus, basically nullifying the opportunity to create nested Hashes and assign them with Hash literals.

Most helpful comment

0_i32 is a (Int32 | Float32) due to a definition of Union. Any integer or float have a union type because union type "has a room" for storing int32 or float32 value.
Now, {"frenzy" => 0} is not a Hash(String, Int32 | Float32) because Hash is a complex object and Hash(String, Int32) has a different memory layout from Hash(String, Int32 | Float32).

All 5 comments

This is expected. {"frenzy" => 0} isn't the same type as Hash(String, Int32 | Float32), thus it fails. You would have to cast the hash as the type you want for it to work. I.e. {"frenzy" => 0} of String => Int32 | Float32

Closing because an answer was given and this was already replied (in other issues) many times before.

You would have to cast the hash as the type you want for it to work. I.e. {"frenzy" => 0} of String => Int32 | Float32

That's like saying we need to do

p.buffs["frenzy"] = 0  of Int32 | Float32

Which isn't valid code, and there is no need, because we already specified the union :). The value will accept Int32 or Float32. The same should be for when we are creating Hashes! The value should map to the same type we already specified, in this case Hash(String, Int32 | Float32). But why do I even bother replying, you don't listen to me at all. Always saying I'm wrong, etc, etc. I'm not here to put down the language FFS, I'm here to offer suggestions on ways to improve it!

I don't even want to post or engage in discussion anymore, I'd honestly just rather PM bcard on the forums with these suggestions/issues. It's pointless, the animosity you guys have against me is not needed.

0_i32 is a (Int32 | Float32) due to a definition of Union. Any integer or float have a union type because union type "has a room" for storing int32 or float32 value.
Now, {"frenzy" => 0} is not a Hash(String, Int32 | Float32) because Hash is a complex object and Hash(String, Int32) has a different memory layout from Hash(String, Int32 | Float32).

it ain't very dev friendly but it's legit from technical perspective...

Was this page helpful?
0 / 5 - 0 ratings