The frustrating lack of container traits in Rust
This is gonna be a short post. I'm tired. I'm in God damned Twin Falls, Idaho. I'm a day away from home.
(Author's note: guess I blew the whole βshortβ thing, because of course I did)
I want to talk about something Python does bettet than Rust, even whilst not having a 1:1 mapping of the trait concept.
Assumptions
I assume you know what a trait in Rust is; if not, go find out (or by example if you prefer).
I also assume you know what an Abstract Base Class β an ABC β in Python is. If not, here's some quick information.
With that out of the way...
My gripe
The ABC concept doesn't 1:1 map to traits, but there are similarities.
Notably, I want to point to the fact Python's collections have ABC's, but Rust has no base traits for its containers.
Why is this a problem?
A unified interface
In Rust, traits more or less specify an interface.None of the standard containers implement container traits.
This means there is no unified container interface.
This may not seem like a problem, but it makes for some annoyances.
Genericity in accepted containers
Consider you want to implement something in Rust that takes any mapping container at all.
This is pseudocode-ish, but you should get the idea. The idea here is we extend all mappings with a new method:
trait dyn Map {
fn replace(&mut self, key: Self::Key, value: Self::Value) {
self.remove(key);
self.insert(key, value);
}
}
Obviously, such a trait like Map
would be super useful in this case.
But it doesn't exist!
You have to roll your own (example stolen from Reddit):
use std::collections::{HashMap, BTreeMap};
use std::hash::Hash;
trait MapExtend {
type Key;
type Value;
fn replace(&mut self, k: Self::Key, v: Self::Value);
}
impl<K: Eq + Hash, V> MapExtend for HashMap<K, V> {
type Key = K;
type Value = V;
fn replace(&self, k: Self::Key, v: Self::Value) {
self.remove(k);
self.insert(k, v);
}
}
impl<K: Ord, V> MapExtend for BTreeMap<K, V> {
type Key = K;
type Value = V;
fn replace(&self, k: Self::Key, v: Self::Value) {
self.remove(k);
self.insert(k, v);
}
}
fn main() {
let mut my_hashmap: HashMap<i32, f32> = HashMap::new();
my_hashmap.insert(42, 23);
my_hashmap.replace(42, 69);
println!("{}", my_hashmap.get(42).unwrap());
let mut my_btreemap: BTreeMap<i32, f32> = BTreeMap::new();
my_btreemap.insert(420, 31337);
my_btreemap.replace(420, 1337);
println!("{}", my_btreemap.get(420).unwrap());
But this sucks! It means that all containers now have to be bent to work with your funky little trait, rather than there be an official interface. It isn't difficult... but it shouldn't be like this.
And of course, it means tons of boilerplate.
Python's ABC's
As for what Python can do here; you could monkeypatch the relevant ABC (although I don't know if it would affect built-in containers... there's a lot of magick in the system I don't quite understand), but you can also subclass containers as needed.
You can also check if a container matches a type, like so:
from collections.abc import MutableSequence
def rotate_seq_right(seq):
if not isinstance(seq, MutableSequence):
raise TypeError("not a MutableSequence")
elem = seq.pop(0)
seq.append(elem)
And this will work with any mutable sequence, and give you a diagnostic if your type isn't one.
How did Python, a duck-typed language, do better than Rust here???
Simplified container implementation
In Python, ABC's specify what a container implements. If you inherit from the Mapping
ABC, your container is a Mapping
, full stop.
This means that extra functionality can be had for free just by inheriting from an ABC, which greatly simplifies container implementations. You can implement any type of tree, any type of mapping at all, you could even make a keyed linked list if you wanted (don't do this, please). By implementing a few methods, your type is first-class with Dict
, which is also a mapping.
Instead, you have to implement it all by hand and hope it conforms to the interface users are expecting. Fun!!!
Conclusion
Rust, please implement container traits? Please? It would be super useful and helpful and it would enable everyone to extend all containers easily as well as other funky shenanigans like passing containers as generics without specifying the container.
Pretty please? π₯Ίπ
β Elizabeth Ashford (Elizafox) Fedi (elsewhere): @Elizafox@social.treehouse.systems Tip jar: PayPal || CashApp || LiberaPay