What is the canonical way in Crystal to insert a key into a Hash, but throw an error if the mapping did exist before. In principle, code like this:
map = Hash(String, Int32).new
if map.has_key?("foo")
raise "Mapping already exists"
else
map["foo"] = 42
end
I wondered if is it possible without hashing twice? I came up with this idea, but it is hard to read IMHO:
map = Hash(String, Int32).new
if map.put("foo", 42) {}
raise "Mapping already exists"
end
(If the mapping exists, Map#put
returns the old value. Otherwise, the result of the block is called, which is nil
and evaluates to false
.)
Is there a recommended pattern?
Performance wise you already arrived at the optimal solution. I would probably use the fact that block calls get inlined to rewrite it slightly:
def update(hash, key, value)
hash.put(key, value) { return }
raise "Duplicate entry"
end
However there's an important pitfall here: The hash still gets updated in the error condition:
hash = {"foo" => "bar"}
update(hash, "baz", "quux")
pp hash # => {"foo" => "bar", "baz" => "quux"}
update(hash, "foo", "hello") rescue puts "Failed updating foo" # => Failed updating foo
pp hash # => {"foo" => "hello", "baz" => "quux"}
Without breaking into internal APIs I don't think there's a way to avoid this. In practice a hash lookup is not very expensive, after all its a data structure optimized for looking up a value by a key. Especially since you seem to be okay with raising in your API, which is a much more expensive operation in relation. So personally I would go for something like your first example.