I'm trying to make a custom serde helpers library, but I'm not sure how to deserialize a seq of hex into a optional Vec of ObjectId. (ObjectId is from bson::oid)
The thing is I want the Vec to be None if the hex seq length is 0. (so empty)
Thanks in advance!
pub mod option_vec_object_id_as_hex_seq {
use bson::oid::ObjectId;
use serde::{ser::SerializeSeq, Serializer};
use std::result::Result;
/// Deserializes a [`Option<Vec<ObjectId>>`] from a seq of hex
// pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<ObjectId>>, D::Error>
// where
// D: Deserializer<'de>,
// {
// // TODO: Convert hex seq into Option<Vec<ObjectId>>
// }
/// Serializes a [`Option<Vec<ObjectId>>`] as a seq of hex
pub fn serialize<S: Serializer>(
val: &Option<Vec<ObjectId>>,
serializer: S,
) -> Result<S::Ok, S::Error> {
match val {
Some(val) => {
let mut seq = serializer.serialize_seq(Some(val.len()))?;
for e in val {
seq.serialize_element(&e.to_hex())?;
}
seq.end()
}
None => serializer.serialize_seq(None)?.end(),
}
}
}
I've tried to use Visitor, but I had problem with creating a Vec.
Edit:
Here is expected input:
["6397f513d5e4a64eda84aa37", "6397f513d5e4a64eda84aa39"]
For expected output:
vec![ObjectId("6397f513d5e4a64eda84aa37"), ObjectId("6397f513d5e4a64eda84aa37")]
If the input is an empty array:
[]
Output would be:
None
This is actually much easier than you might think. You can just deserialize Vec<ObjectId> then check if it has any items. You don't need to worry about this allocating memory on the heap since it should either be creating it with the sequence size hint or by filling an empty Vec. And since an empty Vec does not allocate memory on the heap, this can be kept on the stack in most cases.
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
let sequence = <Vec<T>>::deserialize(deserializer)?;
if sequence.len() == 0 {
return Ok(None);
}
Ok(Some(sequence))
}
Rust Playground
If you want to use a Visitor it is just the same thing, but a bit longer to write.
struct ItemVisitor<'a, T> {
_phantom: PhantomData<&'a T>,
}
impl<'de, 'a: 'de, T> Visitor<'de> for ItemVisitor<'a, T>
where
T: Deserialize<'de>,
{
type Value = Option<Vec<T>>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a list of objects")
}
fn visit_seq<V>(self, mut access: V) -> Result<Self::Value, V::Error>
where
V: SeqAccess<'de>,
{
let mut items = None;
while let Some(next) = access.next_element::<T>()? {
match &mut items {
None => items = Some(vec![next]),
Some(values) => values.push(next),
}
}
Ok(items)
}
}
pub fn vec_or_none<'de, D, T>(deserializer: D) -> Result<Option<Vec<T>>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + 'de,
{
deserializer.deserialize_seq(ItemVisitor {
_phantom: PhantomData,
})
}
Rust Playground
Related
I am trying to implement a Visitor pattern in Rust.
I am not able to find a way to support two visitors which return different return value types.
Playground link
trait Visited<R> {
fn accept (self: &Self, v: &dyn Visitor<R>) -> R;
}
trait Visitor<R> {
fn visit_a(&self, a: &A<R>) -> R;
fn visit_b(&self, b: &B) -> R;
}
I've implemented two data structures that can be visited.
// ---- A ----
struct A<R> {
value: String,
b: Box<dyn Visited<R>>,
}
impl<R> Visited<R> for A<R> {
fn accept (&self, v: &dyn Visitor<R>) -> R {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl<R> Visited<R> for B {
fn accept(&self, v: &dyn Visitor<R>) -> R {
v.visit_b(self)
}
}
This worked okay when I just had a concrete visitor.
struct Visitor1 {}
impl Visitor<String> for Visitor1 {
fn visit_a(&self, a: &A<String>) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
However, the whole point of the Visitor pattern is to let multiple algorithm to be applied against the data structure.
When I wanted to add another visitor, I couldn't figure out how to make it work.
struct Visitor2 {}
impl Visitor<i32> for Visitor2 {
fn visit_a(&self, a: &A<i32>) -> i32 {
123
}
fn visit_b(&self, b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: Box::new(B{ value: 32 })
};
let v1 = Visitor1{};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2{};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
The type of a is inferred as A<String>, and the a.accept(&v2) caused a type mismatch error.
I'd like to tell a to be visited by Visitor1 and Visitor2. How can I do this?
If you return only 'static types, you can use type erasure. The idea is to create a trait, ErasedVisitor, that is not generic and instead return Box<dyn Any>, and implement this trait for all Visitors and use it internally. This mean, though, that you cannot use a generic parameter, only an associated type (otherwise you get "unconstrained type parameter":
trait Visited {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any>;
fn accept<V: Visitor>(&self, v: &V) -> V::Result
where
Self: Sized,
{
*self.accept_dyn(v).downcast().unwrap()
}
}
impl<T: ?Sized + Visited> Visited for &'_ T {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
impl<T: ?Sized + Visited> Visited for &'_ mut T {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
impl<T: ?Sized + Visited> Visited for Box<T> {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
T::accept_dyn(&**self, v)
}
}
trait Visitor {
type Result: 'static;
fn visit_a(&self, a: &A) -> Self::Result;
fn visit_b(&self, b: &B) -> Self::Result;
}
trait ErasedVisitor {
fn visit_a(&self, a: &A) -> Box<dyn Any>;
fn visit_b(&self, b: &B) -> Box<dyn Any>;
}
impl<V: ?Sized + Visitor> ErasedVisitor for V {
fn visit_a(&self, a: &A) -> Box<dyn Any> {
Box::new(<Self as Visitor>::visit_a(self, a))
}
fn visit_b(&self, b: &B) -> Box<dyn Any> {
Box::new(<Self as Visitor>::visit_b(self, b))
}
}
struct A {
value: String,
b: Box<dyn Visited>,
}
impl Visited for A {
fn accept_dyn(&self, v: &dyn ErasedVisitor) -> Box<dyn Any> {
v.visit_a(self)
}
}
Playground.
But if you can, the best way is to use static polymorphism (generics) instead of dynamic dispatch.
If you want your Visited trait to support multiple return types, you cannot tie the return type to the object itself. Currently, the return type is a generic, so every object has the return type hard-coded into it.
To solve this, you can remove the return type generic from the Visited trait and instead attach it to the accept function.
This has one drawback though: You can no longer create trait objects with this trait. That makes sense, because once cast to a dyn Visited, Rust no longer knows which type it is, making it impossible for the compiler to compile the accept function for all required types. It looses the knowledge between the compilated function and the types that it will get called with.
Normally, this is fine, but in your case, A holds a Box<dyn Visited>. This is impossible, due to the reasons described above.
So if we change Box<dyn Visited> to B, (and do some refactoring with the generics), we can get it to work:
trait Visited {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret;
}
trait Visitor {
type Ret;
fn visit_a(&self, a: &A) -> Self::Ret;
fn visit_b(&self, b: &B) -> Self::Ret;
}
// ---- A ----
struct A {
value: String,
b: B,
}
impl Visited for A {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl Visited for B {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_b(self)
}
}
struct Visitor1 {}
impl Visitor for Visitor1 {
type Ret = String;
fn visit_a(&self, a: &A) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
struct Visitor2 {}
impl Visitor for Visitor2 {
type Ret = i32;
fn visit_a(&self, _a: &A) -> i32 {
123
}
fn visit_b(&self, _b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: B { value: 32 },
};
let v1 = Visitor1 {};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2 {};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
visitor1.visit_a(): HELLO visitor1.visit_b(): 32
123
We can make this a little more convenient and make the actual type a generic of A. In contrast to a dyn Visited, this allows Rust to resolve its exact type at compile time, making it possible to compile it.
trait Visited {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret;
}
trait Visitor {
type Ret;
fn visit_a<T: Visited>(&self, a: &A<T>) -> Self::Ret;
fn visit_b(&self, b: &B) -> Self::Ret;
}
// ---- A ----
struct A<T> {
value: String,
b: T,
}
impl<T: Visited> Visited for A<T> {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_a(self)
}
}
// ---- B ----
struct B {
value: i32,
}
impl Visited for B {
fn accept<V: Visitor>(&self, v: &V) -> V::Ret {
v.visit_b(self)
}
}
struct Visitor1 {}
impl Visitor for Visitor1 {
type Ret = String;
fn visit_a<T: Visited>(&self, a: &A<T>) -> String {
let b = a.b.accept(self);
format!("visitor1.visit_a(): {} {}", a.value, b)
}
fn visit_b(&self, b: &B) -> String {
format!("visitor1.visit_b(): {}", b.value)
}
}
struct Visitor2 {}
impl Visitor for Visitor2 {
type Ret = i32;
fn visit_a<T: Visited>(&self, _a: &A<T>) -> i32 {
123
}
fn visit_b(&self, _b: &B) -> i32 {
456
}
}
fn main() {
let a = A {
value: "HELLO".to_string(),
b: B { value: 32 },
};
let v1 = Visitor1 {};
let s: String = a.accept(&v1);
println!("{}", s);
let v2 = Visitor2 {};
let v: i32 = a.accept(&v2);
println!("{}", v);
}
visitor1.visit_a(): HELLO visitor1.visit_b(): 32
123
I'm writing a library to parse json data that looks like this:
{"x": [[1, "a"], [2, "b"]]}
i.e. I have a key with a list of lists where the inner lists can contain different data types but each inner list has the same sequence of types. The sequence of types for an inner list can change for different json schemas but will be known ahead of time.
The desired output would look something like:
vec![vec![1,2], vec!["a", "b"]]
(with the data wrapped in some appropriate enum for the different dtypes).
I began implementing DeserializeSeed for Vec<DataTypes>, below is some similar pseudo-code.
enum DataTypes {
I32,
I64,
String,
F32,
F64
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: SeqAccess<'de>,
{
let types: Vec<DataTypes> = self.0.data;
let out: Vec<Vec<...>>;
while let Some(inner_seq: S) = seq.next_element::<S>()? { // <-- this is the line
for (i, type) in types.enumerate() {
match type {
DataTypes::I32 => out[i].push(inner_seq.next_element::<i32>()?),
DataTypes::I64 => out[i].push(inner_seq.next_element::<i64>()?),
...
}
}
}
}
My problem is I can't seem to find a way to get SeqAccess for the inner lists and I don't want to deserialize them into something like Vec<serde_json::Value> because I don't want to have to allocate the additional vector.
Please fasten your seat belts, this is verbose.
I'm assuming you want to deserialize some JSON data
{"x": [[1, "a"], [2, "b"]]}
to some Rust struct
struct X {
x: Vec<Vec<Value>>, // Value is some enum containing string/int/float…
}
all while
transposing the elements of the inner lists while inserting into the vectors
checking that the inner vector elements conform to some type passed to deserialization
not doing any transient allocations
At the start, you have to realize that you have three different types that you want to deserialize: X, Vec<Vec<Value>>>, and Vec<Value>. (Value itself you don't need, because what you actually want to deserialize are strings and ints and whatnot, not Value itself.) So, you need three deserializers, and three visitors.
The innermost Deserialize has a mutable reference to a Vec<Vec<Value>>, and distributes the elements of a single [1, "a"], one to each Vec<Value>.
struct ExtendVecs<'a>(&'a mut Vec<Vec<Value>>, &'a [DataTypes]);
impl<'de, 'a> DeserializeSeed<'de> for ExtendVecs<'a> {
type Value = ();
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct ExtendVecVisitor<'a>(&'a mut Vec<Vec<Value>>, &'a [DataTypes]);
impl<'de, 'a> Visitor<'de> for ExtendVecVisitor<'a> {
type Value = ();
fn visit_seq<A>(self, mut seq: A) -> Result<(), A::Error>
where
A: SeqAccess<'de>,
{
for (i, typ) in self.1.iter().enumerate() {
match typ {
// too_short checks for None and turns it into Err("expected more elements")
DataTypes::Stri => self.0[i].push(Value::Stri(too_short(self.1, seq.next_element::<String>())?)),
DataTypes::Numb => self.0[i].push(Value::Numb(too_short(self.1, seq.next_element::<f64>())?)),
}
}
// TODO: check all elements consumed
Ok(())
}
}
deserializer.deserialize_seq(ExtendVecVisitor(self.0, self.1))
}
}
The middle Deserialize constructs the Vec<Vec<Value>>, gives the innermost ExtendVecs access to the Vec<Vec<Value>>, and asks ExtendVecs to have a look at each of the [[…], […]]:
struct TransposeVecs<'a>(&'a [DataTypes]);
impl<'de, 'a> DeserializeSeed<'de> for TransposeVecs<'a> {
type Value = Vec<Vec<Value>>;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct TransposeVecsVisitor<'a>(&'a [DataTypes]);
impl<'de, 'a> Visitor<'de> for TransposeVecsVisitor<'a> {
type Value = Vec<Vec<Value>>;
fn visit_seq<A>(self, mut seq: A) -> Result<Vec<Vec<Value>>, A::Error>
where
A: SeqAccess<'de>,
{
let mut vec = Vec::new();
vec.resize_with(self.0.len(), || vec![]);
while let Some(()) = seq.next_element_seed(ExtendVecs(&mut vec, self.0))? {}
Ok(vec)
}
}
Ok(deserializer.deserialize_seq(TransposeVecsVisitor(self.0))?)
}
}
Finally, the outermost Deserialize is nothing special anymore, it just hands access to the type array down:
struct XD<'a>(&'a [DataTypes]);
impl<'de, 'a> DeserializeSeed<'de> for XD<'a> {
type Value = X;
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
struct XV<'a>(&'a [DataTypes]);
impl<'de, 'a> Visitor<'de> for XV<'a> {
type Value = X;
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let k = map.next_key::<String>()?;
// TODO: check k = "x"
Ok(X { x: map.next_value_seed(TransposeVecs(self.0))? })
}
}
Ok(deserializer.deserialize_struct("X", &["x"], XV(self.0))?)
}
}
Now, you can seed the outermost Deserialize with your desired type list and use it to deserialize one X, e.g.:
XD(&[DataTypes::Numb, DataTypes::Stri]).deserialize(
&mut serde_json::Deserializer::from_str(r#"{"x": [[1, "a"], [2, "b"]]}"#)
)
Playground with all the left-out error handling
Side node: If you can (i.e. if the format you're deserializing is self-describing like JSON) I'd recommend to do the type checking after deserialization. Why? Because doing it during means that all deserializers up to the top deserializer must be DeserializeSeed, and you can't use #[derive(Deserialize)]. If you do the type checking after, you can #[derive(Deserialize)] and #[serde(deserialize_with = "TransposeVecs_deserialize_as_free_function")} x: Vec<Vec<Value>>, and save half of the cruft in this post.
I need to to use custom implementations of Serialize and Deserialize, but i could not figure out how to do something like #[serde(flatten)] does, does anyone know?
Note: i know i could completely re-write the full implementation of the lower elements into the higher one, but the lower elements implement Serialize (and Deserialize), so i am searching for a way to add that to something like serialize_struct.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Nested {
somefield2: String,
}
#[derive(Debug, PartialEq)]
struct TopLevel {
somefield1: usize,
nested: Nested,
}
impl Serialize for TopLevel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// How to do this properly?
let mut do_struct = serializer.serialize_struct("Named", 2)?;
do_struct.serialize_field("somefield1", &self.somefield1)?;
// how to add everything from "self.nested" as the same level as this one?
// JSON example: { somefield1: 0, somefield2: 0 }
return do_struct.end();
}
}
impl<'de> Deserialize<'de> for TopLevel {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
// Same question as in "Serialize", how to do this properly in here?
// Here is currently no example code, because i try to figure out "Serialize" first
todo!();
}
}
Versions used:
serde: 1.0.133
rust: 1.58.1
Note: i have already read Implementing Serialize and tried to search issues / stackoverflow but could not find anything related to that.
You could try doing something like this for Serialize and something similar for Deserialize:
struct FakeStructFlatteningSerializer<'a, SS: SerializeStruct>(&'a mut SS);
impl Serializer<'a, SS: SerializeStruct> for FakeStructFlatteningSerializer<'a, SS> {
type Ok = ();
type Error = SS::Error;
type SerializeStruct = FakeStructFlatteningSerializeStruct<'a, SS>;
// return Impossible for everything else
fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct, Self::Error> {
// ignore name and len!
Ok(FakeStructFlatteningSerializeStruct(self.0))
}
}
struct FakeStructFlatteningSerializeStruct<'a, SS: SerializeStruct>(&'a mut SS);
impl<'a, SS: SerializeStruct> SerializeStruct for FakeStructFlatteningSerializeStruct<'a, SS> {
type Ok = ();
type Error = SS::Error;
fn serialize_field<T: Serialize + ?Sized>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> {
self.0.serialize_field(key, value)
}
fn skip_field(&mut self, key: &'static str) -> Result<(), Self::Error> {
self.0.skip_field(key)
}
fn end(self) -> Result<Self::Ok, Self::Error> {
// ignore!
Ok(())
}
}
impl Serialize for TopLevel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// len needs to include the flattened fields
let mut do_struct = serializer.serialize_struct("Named", 3)?;
do_struct.serialize_field("somefield1", &self.somefield1)?;
self.nested.serialize(FakeStructFlatteningSerializer(&mut do_struct));
return do_struct.end();
}
}
You could alternatively try to figure out how Serde does it; this might be where: https://github.com/serde-rs/serde/blob/dc0c0dcba17dd8732cd8721a7ef556afcb04c6c0/serde_derive/src/ser.rs#L953-L1037, https://github.com/serde-rs/serde/blob/fb2fe409c8f7ad6c95e3096e5e9ede865c8cfb49/serde_derive/src/de.rs#L2560-L2578
I'm trying to write a custom serializer and derializer for a type which is uncomplicated but doesn't have a natural JSON representation.
pub type ArrayKey = [i16; 5];
pub struct ArrayKeyedMap {
the_map: HashMap<ArrayKey, u32>
}
The HashMap, having array keys, can't be represented as a JSON map (which must have string keys). So I want to serialize it as a sequence of pairs. The documentation for Serialization of a sequence is very clear, and the following works:
impl Serialize for ArrayKeyedMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.the_map.len()))?;
for (the_key, the_val) in self.the_map.iter() {
seq.serialize_element(the_key)?;
seq.serialize_element(the_val)?;
}
seq.end()
}
}
This serializes as e.g. [[1,2,3,4,5], 1, [5,4,3,2,1], 2]. The documentation for deserialization of a sequence is not so easy to follow and I can't make the leap between the Visitor and how to retrieve data from a SeqDeserializer. I'm not sure if I'm meant to pass something in, or get a returned value, or something else.
impl<'de> Deserialize<'de> for ArrayKeyedMap
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(SeqDeserializer::new // not sure how to proceed
}
}
How do I deserialize this back into the map with array keys?
Not sure if this is the perfect answer, but this is what worked. The Visitor needs to be implemented as a separate struct, which is a little more complex as the serializer. But the docs warn of this!
impl ArrayKeyedMap {
pub fn new() -> ArrayKeyedMap {
ArrayKeyedMap {
the_map: HashMap::new(),
}
}
}
impl Serialize for ArrayKeyedMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.the_map.len()))?;
for (the_key, the_val) in self.the_map.iter() {
seq.serialize_element(the_key)?;
seq.serialize_element(the_val)?;
}
seq.end()
}
}
struct ArrayKeyedMapDeserializer;
impl<'de> Visitor<'de> for ArrayKeyedMapDeserializer {
type Value = ArrayKeyedMap;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("ArrayKeyedMap key value sequence.")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut new_obj = ArrayKeyedMap::new();
while let Some(key) = seq.next_element()? {
if let Some(value) = seq.next_element()? {
new_obj.the_map.insert(key, value);
} else {
return Err(de::Error::custom(format!(
"Didn't find the right sequence of values in ArrayKeyedMap."
)));
}
}
Ok(new_obj)
}
}
impl<'de> Deserialize<'de> for ArrayKeyedMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(ArrayKeyedMapDeserializer)
}
}
fn edit_map_values(
map1: &mut HashMap<String, i128> || &mut BTreeMap<String, i128>){
for tuple in map1.iter_mut() {
if !map1.contains_key(&"key1") {
*tuple.1 += 1;
}
}
map1.insert(&"key2", 10);
}
How do I write one function that accepts either HashMap and BtreeMap like in the example above?
It is possible to abstract over types by using traits and for your specific use-case, you can take a look at this more constrained example.
use core::{borrow::Borrow, hash::Hash};
use std::collections::{BTreeMap, HashMap};
trait GenericMap<K, V> {
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord;
fn each_mut<F>(&mut self, cb: F)
where
F: FnMut((&K, &mut V));
fn insert(&mut self, key: K, value: V) -> Option<V>;
}
impl<K, V> GenericMap<K, V> for HashMap<K, V>
where
K: Eq + Hash,
{
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord,
{
self.contains_key(k)
}
fn each_mut<F>(&mut self, mut cb: F)
where
F: FnMut((&K, &mut V)),
{
self.iter_mut().for_each(|x| cb(x))
}
fn insert(&mut self, key: K, value: V) -> Option<V> {
self.insert(key, value)
}
}
impl<K, V> GenericMap<K, V> for BTreeMap<K, V>
where
K: Ord,
{
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord,
{
self.contains_key(k)
}
fn each_mut<F>(&mut self, mut cb: F)
where
F: FnMut((&K, &mut V)),
{
self.iter_mut().for_each(|x| cb(x))
}
fn insert(&mut self, key: K, value: V) -> Option<V> {
self.insert(key, value)
}
}
fn edit_map_values<T: GenericMap<String, i128>>(map: &mut T) {
map.each_mut(|(k, v)| {
if k != "key1" {
*v += 1;
}
});
map.insert("key2".into(), 10);
}
fn main() {
let mut hm: HashMap<String, i128> = [("One".into(), 1), ("Two".into(), 2)]
.iter()
.cloned()
.collect();
let mut btm: BTreeMap<String, i128> = [("Five".into(), 5), ("Six".into(), 6)]
.iter()
.cloned()
.collect();
dbg!(&hm);
dbg!(&btm);
edit_map_values(&mut hm);
edit_map_values(&mut btm);
dbg!(&hm);
dbg!(&btm);
}
Way back before the 1.0 release, there used to be Map and MutableMap traits, but they have been removed before stabilization. The Rust type system is currently unable to express these traits in a nice way due to the lack of higher kinded types.
The eclectic crate provides experimental collection traits, but they haven't been updated for a year, so I'm not sure they are still useful for recent versions of Rust.
Further information:
Does Rust have Collection traits?
No common trait for Map types? (Rust language forum)
Associated type constructors, part 1: basic concepts and introduction (blog post by Niko Matsakis)
Generic associated type RFC
While there is no common Map trait, you could use a combination of other traits to operate on an Iterator to achieve similar functionality. Although this might not be very memory efficient due to cloning, and also a bit involved depending on the kind of operation you are trying to perform. The operation you tried to do may be implemented like this:
fn edit_map_values<I>(map: &mut I)
where
I: Clone + IntoIterator<Item = (String, i128)> + std::iter::FromIterator<(String, i128)>,
{
// Since into_iter consumes self, we have to clone here.
let (keys, _values): (Vec<String>, Vec<_>) = map.clone().into_iter().unzip();
*map = map
.clone()
.into_iter()
// iterating while mutating entries can be done with map
.map(|mut tuple| {
if !keys.contains(&"key1".to_string()) {
tuple.1 += 1;
}
tuple
})
// inserting an element can be done with chain and once
.chain(std::iter::once(("key2".into(), 10)))
.collect();
// removing an element could be done with filter
// removing and altering elements could be done with filter_map
// etc.
}
fn main() {
use std::collections::{BTreeMap, HashMap};
{
let mut m = HashMap::new();
m.insert("a".to_string(), 0);
m.insert("key3".to_string(), 1);
edit_map_values(&mut m);
println!("{:#?}", m);
}
{
let mut m = BTreeMap::new();
m.insert("a".to_string(), 0);
m.insert("key3".to_string(), 1);
edit_map_values(&mut m);
println!("{:#?}", m);
}
}
Both times the output is the same, except for the order of the HashMap of course:
{
"a": 1,
"key2": 10,
"key3": 2,
}