How to construct a BTreeMap of fixed values in Rust?

I am writing some tests for an API. This particular API returns a BTreeMap object containing some data.

To test this I have written some code like this:

// part of a unit test
let total_volume = order_book.total_volume();

let mut expected_total_volume = BTreeMap::new();
expected_total_volume
    .insert(
        NotNan::new(100.0).unwrap(), NotNan::new(20.0).unwrap()
    );
expected_total_volume
    .insert(
        NotNan::new(120.0).unwrap(), NotNan::new(10.0 + 12.0).unwrap()
    );

assert_eq!(total_volume, expected_total_volume);

There is nothing wrong with this, but one could argue that expected_total_volume should not be mutable. It is an immutable object, the mutability is just there (and required) to construct it by progressively inserting each required data element.

Is there a way to build a BTreeMap like this such that the resulting value is immutable?

Maps in std implement From<[(K, V); N]>.


If this weren't the case, a completely general solution is creating and mutating the object in a smaller scope, by enclosing it in a block specific to the construction, then assigning the value of that block immutably to an outer binding.

Another completely general solution is to create an array/vec (for which literal syntax and a macro is available, respectively), then turn it into an iterator, and .collect() the iterator to the required type.

2 Likes

That's perfect thanks.

This is what I went with:

let expected_total_volume = BTreeMap::from(
    [
        (NotNan::new(100.0).unwrap(), NotNan::new(20.0).unwrap()),
        (NotNan::new(120.0).unwrap(), NotNan::new(10.0 + 12.0).unwrap()),
    ]
);

Note also that anything you can .collect() into implements FromIterator. So even without an array-based From, you can always from_iter from an array:

let expected_total_volume = MyCollection::from_iter(
    [
        (NotNan::new(100.0).unwrap(), NotNan::new(20.0).unwrap()),
        (NotNan::new(120.0).unwrap(), NotNan::new(10.0 + 12.0).unwrap()),
    ]
);

as an array of B implements IntoIterator<Item = B>.

(That method is even in the prelude now, so no imports needed.)

4 Likes

Note that the inability to take a &mut _ only applies to any given binding. So on the one hand, an alternative to collecting or the like is to just move the variable to a new binding:

let mut expected_total_volume = BTreeMap::new();
expected_total_volume.insert(..);
expected_total_volume.insert(..);
let expected_total_volume = expected_total_volume;

And on the other hand, be aware that the value is not "intrinsically immutable" or the like,[1] because further down one can always

let mut changed_my_mind = expected_total_volume;
changed_my_mind.insert(..);

  1. regardless of how it is constructed ↩ī¸Ž

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.