Option with mutable array element reference

fn foo(mut array: Vec<i32>, increment: bool) {
    let mut cache = array.first_mut();
    if increment {
        let value = cache.get_or_insert_with(|| {
            array.push(0);
            array.last_mut().unwrap()
        });
        **value += 1;
    }
}

I'd like to keep a mutable reference to an array element that may be lazily inserted. I'm trying to do this with an Option that references into the array, but can't figure out how to avoid multiple mutable borrows.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=056d839624e3da832b7268cf4ab66f9d

I think the multiple mutable borrows would actually be safe, because array.push only happens if cache is None (there is actually no mutable reference to array). But I don't know how to express that :confused:

In this case, you need to compute the mutable reference after the insertion has already (potentially) happened. I'm not sure what exactly you are trying to do, though, as the code you posted is rather weird (it refers to the first element rather than the last one, and it takes the array by value?!) and contains several other errors in addition to the multiple borrow.

E.g. note that you can't use get_or_insert_with here, because it's useless. Option::get_or_insert_with() only changes the contents of the Option itself. It doesn't magically dereference the Option's contents if it happens to be a reference. Therefore, you can't use it to conditionally insert into the Vec. If you use it, you will only change the temporary Option, the return value of array.first_mut(), and not the array.

Are you trying to do this (playground)?

fn foo(array: &mut Vec<i32>, increment: bool) {
    if increment {
        match array.last_mut() {
            Some(last) => *last += 1,
            None => array.push(1),
        }
    }
}

Thanks for the reply! Sorry that the code is so weird. I tried to isolate the code from a larger function but that kind of obscures what I'm trying to do. This playground has the actual function, but it won't run there because of undefined structs (and the multiple mutable borrows). In this playground, text_plan.steps is the array and param_step is the cached element reference.

I'm not sure why get_or_insert_with wouldn't work, since it calls array.push? In any case the playground above uses match and Option::replace instead which may be clearer?

I don't think your suggestion works in my case because it's actually two steps: first param_step is set to an element if we can find it, then it is set to a newly created element if we need to insert into it.

I feel like I'm writing code that is very "C style"; maybe there is a more Rust-y way to express what I'm trying to do?

I'm not sure where you see this push call?

It doesn't.

In this snippet, the closure to get_or_insert_with calls array.push. get_or_insert_with does not push into array, but the closure does; maybe that's the confusion?

I'm really curious about how to keep a reference to an element that might get created later. In C++ I might do something like

cache = NULL;
for (int i = 0; i < array.size(); i++) {
  if (matches_cache(&array[i]))
    cache = &array[i];
}

for (int i = 0; i < inputs.size(); i++) {
  if (needs_cache_elem(inputs[i])) {
    if (!cache) {
      array.push_back(new_element());
      cache = array.back();
    }
    cache.do_something(inputs[i]);
  }
}

This snippet has a second loop over inputs that is more similar to my actual structure.

Ah, ok. This is a bit funky though. I would just go for this:

if increment {
    if cache.is_empty() {
        cache.push(0);
    }
    *array.first_mut().unwrap() += 1;
}

This seems a lot clearer than your code.

1 Like

Thanks it is working now! This is the code now:

fn foo(array: &mut Vec<i32>, increment: bool) {
    let mut cache = array.first_mut();
    if increment {
        if cache.is_none() {
            array.push(0);
            cache = array.last_mut();
        }
        *cache.unwrap() += 1;
    }
}

I think I was overthinking things trying to rearrange the unwrap, but that made the lifetimes overlap.

That code is exactly equivalent with what Alice wrote above – I'd suggest you go with her solution because it is less complicated. Additionally, you could replace the *array.first_mut().unwrap() part with just array[0].

My use case does not have the element in a predictable location, it can be any element in the array. cache is basically the index the element exists at if any (FWIW, I think I also could have worked around the multiple mutable refs by storing indexes instead).

I would probably go for storing an index then.

1 Like

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.