Search code examples
crystal-lang

Crystal: detect if a dictionary insert overwrites a key, but without hashing twice


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?


Solution

  • 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"}
    

    https://carc.in/#/r/bn1o

    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.