Altering the struct field within Vec of Option<struct>

Dear All,

Can't figure out how to elegantly change the field of the struct which is embedded into a vector of Option. Here is the variation of the code i tried to write, but it does not work. I thought of extracting the value with take(), changing the field and then inserting the Option back into the Vec, but this does not seem to be a performant solution...
Could you, please, suggest a good one?

fn main() {
    let mut vec_try = vec![Some(Val {val_int: 8, val_str: String::from("try1")}), Some(Val {val_int:20, val_str: String::from("try2")}), Some(Val {val_int:33, val_str: String::from("try3")})];
    if let Some(value) = &vec_try[1] {
        Some(Val {val_int: 22, ..value});
    }
    println!("The value is: {:?}", vec_try[1]);
}

#[derive(Debug)]
struct Val {
    val_int: u8,
    val_str: String,
}
1 Like

Why would that not be performant? No matter how you exactly solve the problem, the underlying steps will be exactly that.

let mut val = obs[idx].take();
val.val_int += 2;
obs[idx].replace(val);
2 Likes
    if let Some(value) = &mut vec_try[1] {
        value.val_int = 22;
    }

Or more explicitly,

    if let &mut Some(ref mut value) = &mut vec_try[1] {
        value.val_int = 22;
    }

Playground.

Read more about pattern matching here -- e.g. identifier patterns and binding modes.

2 Likes

You can also take an exclusive reference to the Option, match on it and change the field directly:

fn main() {
    let mut vec_try = vec![Some(Val {val_int: 8, val_str: String::from("try1")}), Some(Val {val_int:20, val_str: String::from("try2")}), Some(Val {val_int:33, val_str: String::from("try3")})];
    if let Some(value) = &mut vec_try[1] {
        value.val_int = 22;
    }
    println!("The value is: {:?}", vec_try[1]);
}

#[derive(Debug)]
struct Val {
    val_int: u8,
    val_str: String,
}

Playground

Not however that this doesn't allow you to use the struct update syntax, since this would move the value out of reference (even if to put it back right away).

(@quinedot beat me to this :))

2 Likes

If you want to mutate it, you can't take a &, you need a &mut

let mut vec_try = vec![Some(Val {val_int: 8, val_str: String::from("try1")}), Some(Val {val_int:20, val_str: String::from("try2")}), Some(Val {val_int:33, val_str: String::from("try3")})];
if let Some(value) = &mut vec_try[1] {
    value.val_int = 22;
}
println!("The value is: {:?}", vec_try[1]);

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bb3504bd89ba13ee18bd482354f66d2a

2 Likes

Thank you for the reply! This is interesting... But i do not understand why is that.

I would think about take() operations as: 1) move the pointer to struct somewhere, 2) assign None to the current array[index], 3) change the field, 4) assign the pointer to struct back into array[index].
As opposed to just following a pointer (aka struct->field) and changing the field of the struct.

Please, explain where my understanding goes wrong...

1 Like

There's no "pointer". The struct itself is moved from the vector storage and then back.

1 Like

Again, not sure i understand...
If the struct is on the heap, how is it moved out and back in without a "pointer"?
Does move occur in this case as well:

if let Some(value) = &mut vec_try[1] {
        value.val_int = 22;
    }
1 Like

It can be moved from heap to stack, no problem. You have to follow the pointer (which is stored in Vec) to reach it, of course.

It doesn't, since you use the pointer to modify struct in-place, not to move it from this place.

1 Like

Thank you, it is much more clear now.
But there was a reply above saying that the move occurs in any case. You say that this is not true - i.e., i see some disagreement between what you and RedDocMD wrote.
Thus, was my original guess that the modification of the struct in place is more performant correct?

1 Like

Move occurs in any case if you can't get with only replacing some fields, i.e. if you need the whole struct to get the new struct. If you can replace it field-by-field, this can be done in-place, without move.

1 Like

Oh, i see now that the answer of RedDocMD related to this syntax:

Some(Val {val_int: 22, ..value});

Everything seems clear now.
Thank you all for these replies!

1 Like