I was building a generic data store with Rust and I needed to implement a heterogeneous collection of keys and values. Essentially what I needed was a dictionary, but with values of dynamic type, like both strings and integers at the same time.
Rust is a statically typed language and, due to the memory safety guarantees we are given, all values of some type must have a known, fixed size at compile time, therefore we are not allowed to create a collection of multiple types. However, dynamically sized types also exist, and in this article I’ll show how to use them.
Say we have a
HashMap and we want to add more than one value type to it.
In the example above, the type of
HashMap<&str, &str>. In other words, both keys and values are of type
What if we want the values to be of type
&str and, say,
This won’t work:
If we try it, we get this compile time error:
So how do we insert multiple value types in a
HashMap? We have several options, each of them with its own trade-offs.
Option #1: Use an
We can define our own
enum to model our value type, and insert that into the hashmap:
This is similar to a union type. By inserting values of type
Value::*, we are effectively saying that the map can accept types that are either string, integer, or any other composite type we wish to add.
Option #2: Use a
We can wrap our types in the
This doesn’t compile right away. If we try to run this, we get a “mismatched types” error:
Luckily we can fix this by explicitly declaring the type of our map:
This works because we are actually storing instances of
Box, not primitive types;
dyn Display means the type of the trait object
Display. In this case,
Display happens to be a common trait between
You may wonder what would happen if we were to use the type
dyn Display without the
Box wrapper. If we try that, we’d get this nasty error:
“Rust needs to know how much memory to allocate for any value of a particular type, and all values of a type must use the same amount of memory.”
Box<T> type is a pointer type. It lets us allocate data on the heap rather than the stack, and keeps a reference to the data in the stack in the form of a pointer, which is of fixed size.
(Not an) Option #3: Use separate maps for each type
Here we’re not actually using a
HashMap with separate types, but rather two maps, each with its own type. It’s a bit more verbose and perhaps not the solution you’re looking for, but it’s worth keeping in mind that this works too:
It feels much simpler! And the output is naturally the same:
Rust is very strict when it comes to polymorphic types. As you’ve seen, there are ways to achieve it, but they don’t feel as straightforward as with other dynamic languages such as Ruby or Python. Sometimes though it’s useful to make one step back and look at the actual problem we’re trying to solve. Once I did that, I realized that I didn’t necessarily have to limit myself to a single data structure, so I went for the last option.
I’m still a beginner with Rust, so I might have missed on a better solution. Trait Objects could be one: I’ve experimented with them, but they weren’t quite was I was looking for. If you have any suggestions or know of other possible solutions, feel free to comment below!
Update: @alilleybrinker on Twitter pointed out two caveats to be aware of. One is about the meaning of the
'static bound: when used on a generic type, any references inside the type must live as long as
'static. However, by adding
'static we are also effectively saying that the values inside the
Box won’t contain references. The other caveat is that, when using
dyn Display, the original types are erased, so the available methods are only those known from the