I've been working on a crate that defines macros for initialising collections. The "big" feature is that you can use the ..
operator to include multiple values at the same time, from an IntoIterator
. The usage is supposed to mirror to how you can already initialise structs in Rust, e.g.
let my_struct = MyStruct {
foo: 1,
bar: 2,
..Default::default()
}
There's no problem for things like sets and vectors because values are always added, and never replace existing ones:
let set = hash_set!(1, 3, ..others, 6, 7, 8);
But for maps, I realised that the semantics of ..
are subtly different from the struct initialisation semantics. In particular, the order they are added.
In velcro, items are added to maps in order, from left to right, regardless of whether the key already exists, so this:
let map = hash_map! {
'a': 1,
..('a'..='c'): 0,
};
is equivalent to:
let map = hash_map! {
'a': 0,
'b': 0,
'c': 0,
};
While this:
let map = hash_map! {
..('a'..='c'): 0,
'a': 1,
};
is equivalent to:
let map = hash_map! {
'a': 1,
'b': 0,
'c': 0,
};
The values added later overwrite the values added before.
Now contrast this with a similar example initialising a Rust struct:
#[derive(Default, Debug)]
struct Foo { a: i32, b: i32, c: i32 }
let foo = Foo {
a: 1,
..Foo::default()
};
println!("{:?}", foo); // Foo { a: 1, b: 0, c: 0 }
For structs, the spread values must be the last thing and they never override fields that have been explicitly set.
So I'm a little bit torn on the issue. I think my options are:
- Use different syntax, to avoid confusion
- Insert map values from right to left instead.
- Keep it as it is ¯\(ツ)/¯
Would you find it surprising if the order was reversed? For example, would you expect the following?
let map = hash_map! {
'a': 0,
'a': 1,
..('a'..='c'): 2,
};
assert_eq!(map[&'a'], 0);