Best solution for mutating collection weights while iterating over its indexes

I'm currently using the petgraph library and attempting to do some graph transformations, where the indexes of the graph to transform are passed around separately from the weights.

petgraph's API (similar to HashMap's) requires a &self to get indexes and a &mut self to mutate weights. Of course, both of these cannot occur at the same time due to Rust's borrow checker. However, there's no particular reason for this to be the case (as long as I don't add or remove nodes from the graph), as the indexes are valid even if I mutate the weights.

Looking at Mutating values while iterating a map, based on keys - help - The Rust Programming Language Forum (rust-lang.org), a suggestion was to use a RefCell. Another alternative that I thought of was just pass index-weight pairs around. However both do not seem as clear/intuitive as I'd like.

Below is my sample code for the solutions. Note that I'm using HashMap as the example, as petgraph provides similar APIs. I'm wondering if there are better solutions that exist.

    use std::{cell::RefCell, collections::HashMap};
    
    #[test]
    fn goal() {
        let mut graph: HashMap<usize, i32> = HashMap::from([(0, 10), (123, 456)]);

        // Some sort of filter (e.g. node has more than x successors)
        let keys = graph.keys().filter(|&i| *i > 10).collect::<Vec<_>>();

        fn transform(indexes: Vec<&usize>, graph: &mut HashMap<usize, i32>) {
            for index in indexes {
                if let Some(data) = graph.get_mut(index) {
                    *data = 999;
                } else {
                    panic!();
                }
            }
        }

        // Borrow error
        // transform(keys, &mut graph);
    }

    #[test]
    fn ref_cell_solution() {
        let graph: HashMap<usize, RefCell<i32>> =
            HashMap::from([(0, 10.into()), (123, 456.into())]);

        // Some sort of filter (e.g. node has more than x successors)
        let keys = graph.keys().filter(|&i| *i > 10).collect::<Vec<_>>();

        fn transform(indexes: Vec<&usize>, graph: &HashMap<usize, RefCell<i32>>) {
            for index in indexes {
                if let Some(data) = graph.get(index) {
                    *data.borrow_mut() = 999;
                } else {
                    panic!();
                }
            }
        }

        transform(keys, &graph);
        assert_eq!(graph, HashMap::from([(0, 10.into()), (123, 999.into())]))
    }

    #[test]
    fn elements_solution() {
        let mut graph: HashMap<usize, i32> = HashMap::from([(0, 10), (123, 456)]);

        // Some sort of filter (e.g. node has more than x successors)
        let elements = graph.iter_mut().filter(|(k, _)| *k > &10);

        fn transform<'a, T>(elements: T)
        where
            T: Iterator<Item = (&'a usize, &'a mut i32)>,
        {
            for (_, v) in elements {
                *v = 999;
            }
        }

        // Borrow error
        transform(elements);
        assert_eq!(graph, HashMap::from([(0, 10), (123, 999)]));
    }

(Playground)

Output:


running 3 tests
test goal ... ok
test ref_cell_solution ... ok
test elements_solution ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


Errors:

   Compiling playground v0.0.1 (/playground)
warning: unused imports: `cell::RefCell`, `collections::HashMap`
 --> src/lib.rs:1:15
  |
1 |     use std::{cell::RefCell, collections::HashMap};
  |               ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: `playground` (lib) generated 1 warning (run `cargo fix --lib -p playground` to apply 1 suggestion)
warning: unused variable: `keys`
 --> src/lib.rs:8:13
  |
8 |         let keys = graph.keys().filter(|&i| *i > 10).collect::<Vec<_>>();
  |             ^^^^ help: if this is intentional, prefix it with an underscore: `_keys`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: variable does not need to be mutable
 --> src/lib.rs:5:13
  |
5 |         let mut graph: HashMap<usize, i32> = HashMap::from([(0, 10), (123, 456)]);
  |             ----^^^^^
  |             |
  |             help: remove this `mut`
  |
  = note: `#[warn(unused_mut)]` on by default

warning: function `transform` is never used
  --> src/lib.rs:10:12
   |
10 |         fn transform(indexes: Vec<&usize>, graph: &mut HashMap<usize, i32>) {
   |            ^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` on by default

warning: `playground` (lib test) generated 3 warnings (run `cargo fix --lib -p playground --tests` to apply 2 suggestions)
    Finished test [unoptimized + debuginfo] target(s) in 1.67s
     Running unittests src/lib.rs (target/debug/deps/playground-519d22b11db3567b)
   Doc-tests playground

Build a separate collection of the updated weights, and assign them in a second pass over the graph.


Edit: what's wrong with the solution you named elements_solution? If the collection provides an iterator over known-disjoint projections (eg. immutable keys and mutable values), then you should just use that iterator, it's the most idiomatic (as it conveys the most concretely what it is you are trying to do).

Ideally I would be able to hold references to multiple sets of indexes separately, then mutate the weights of those indexes on-demand. I don't see this being achievable the elements_solution.

I quite like independently building a collection of the updated weights, then assigning them in a second pass. Could this be better abstracted with a typestate pattern? By being able to "lock" the graph. When a graph is "locked", I can then take both a ref to the indexes and a mut ref to the weights, but the graph loses its add/remove node methods. This can help mitigate invalid indexes to nodes.