Assigning to an out param using mem::replace or deref

Hi there,

I’m trying to make sure I fully understand how ownership works in Rust and I’ve run into something that’s stumped me.

I wanted to implement a reset function where you could pass a &mut object and assign a new default value to it - essentially an out param. (I know returning a value is the more idiomatic approach but this is just a toy example).

Now in order for this to work, I learned that you must use mem::replace to ensure there is always a valid owner (I found this answer to this SO question quite helpful - https://stackoverflow.com/questions/27197938/memreplace-in-rust). This now makes sense to me and is the code present in reset_replace below.

What I don’t understand is how the code in reset_deref is working.

From what I can tell the line *my_type = new_type is performing a Copy, but based on what I’ve read, structs are not copyable by default and must use either the #[derive(Copy)] (and/or) #[derive(Clone)] trait. I am coming from C++ and maybe that part of my brain is confusing me :see_no_evil:.

Is *my_type giving me the binding as opposed to the value? (is that the correct terminology?). And which is the preferred approach, the code used in _replace or _deref?

I’d be incredibly grateful if someone could please put me out of my misery :stuck_out_tongue:

(I pasted the code into Godbolt to see what the generated assembly looks like and it appears to be quite similar - https://gcc.godbolt.org/z/TSs581)

use std::mem;

pub struct MyOtherType {
    a_float: f64,
}

pub struct MyType {
    an_int: u64,
    a_string: String,
    another_type: MyOtherType,
}

pub fn reset_replace(my_type: &mut MyType) {
    let new_type = MyType {
        an_int: 7,
        a_string: String::new(),
        another_type: MyOtherType { a_float: 2.0 },
    };
    mem::replace(my_type, new_type);
}

pub fn reset_deref(my_type: &mut MyType) {
    let new_type = MyType {
        an_int: 4,
        a_string: String::new(),
        another_type: MyOtherType { a_float: 1.0 },
    };
    *my_type = new_type;
}

Thank you very much for your help! :slight_smile:

1 Like

The meaning of the prefix asterisk on *my_type differs depending on whether it’s on the left-hand or the right-hand side of an assignment =. On the RHS it means “the value referred to by this reference,” while on the LHS, it means “the assignment destination is the value referred to by the reference.” (We inherited this subtle detail from the C family of languages, so if you’re coming from C++, that might not sound odd so far.)

A key difference from C++ is that the line *my_type = new_type is performing a move, not a copy. From the programmer’s perspective, the contents of the new_type value are being taken and stuffed into the memory referenced by my_type, leaving new_type invalid and inaccessible (because, as you note, you have not derived Copy).

Your note about mem::replace is absolutely correct, but only matters if you need new_type to survive the assignment. For example, if it were a field of a longer-lived object, the compiler would insist that you need to put something valid in, to replace the value you moved into *my_type:

pub fn a_different_reset(my_type: &mut MyType, value_source: &mut MyType) {
    *my_type = *value_source;
    // This is illegal if value_source is not Copy, because you have
    // destroyed it by moving its contents into my_type -- but you
    // are not its owner! So the compiler would make you put something
    // back so you are not stealing from the actual owner:
    *value_source = something;
    // That's what mem::replace does in this case.
}

In your case, because the reset value is being moved from a local that you don’t try to use again, you don’t have to use mem::replace.

4 Likes

I’ll just drop a reference to this other thread, where I detailed how memory swapping happens in Rust, which implied detailing the move semantics of the language, and mem::replace vs. a dereferencing write.

Hi cbiffle!

Thank you very much for your response - it does make sense that *my_type = new_type is a move, not a copy, I should have realized in hindsight.

I do have one question about the new function you added - a_different_reset, is there actually a way to use the incoming value_source to reset my_type? The code in the example you gave rightly would not compile, but I’m not actually sure how you’d modify to get it to compile using mem::replace?

Thanks again for the explanation, it’s much clearer in my head now! Much appreciated :slight_smile:

Hi Yandros,

Thank you for including the additional link, I’ll have a read of it now.

Cheers!

Just finished going through the referenced thread, super helpful too, thank you for link!

Great question. What you probably want to do in that case is take value_source by simple (immutable) reference and use clone:

pub fn a_different_reset(my_type: &mut MyType, value_source: &MyType) {
    *my_type = value_source.clone();
}

That way, you haven’t taken anything from the actual owner of what value_source references, you’ve only made a copy.

1 Like

After having a little play around it seems like one way to do it would be like this…

pub fn a_different_reset(my_type : &mut MyType, value_source: &mut MyType) {
    let defaulted = MyType { ..Default::default() };
    let val_src = mem::replace(value_source, defaulted);
    mem::replace(my_type, val_src);
}

value_source would need to be returned to a 'default` value though (as it would be otherwise ‘moved from’)

If there’s another way please let me know!

Thanks again!

1 Like

Haha ah just saw this! This looks much more sensible but interesting to see how the other approach is possible :slight_smile:

Interesting stuff, thanks for clarifying things, I can sleep easy tonight :slight_smile:

This is forcing the user to replace the input value with Default::default(): in that case it is preferred to give value_source by value, and in case someone wanted to call your function with a &mut _ reference, it will be the caller’s choice to use:

  • either reset(&mut target, mem::replace(value_source, Default::default())) if value_source: &mut MyType

  • or reset(&mut target, value_source_opt.take()) if the caller can manage to have value_source_opt: &mut Option<MyType> instead

1 Like

Ah got you, so you could do this…

fn another_different_reset(my_type: &mut MyType, value_source: MyType) {
    mem::replace(my_type, value_source);
}

and call it like so…

{
    let mut my_type1 = MyType::new();
    let my_type2 = MyType::new();

    another_different_reset(&mut my_type1, my_type2);
    // println!("{:?}", my_type2); not allowed, error[E0382]: borrow of moved value: `my_type2`
}

// or..

{
    let mut my_type1 = MyType::new();
    let mut my_type2 = MyType::new();

    another_different_reset(&mut my_type1, mem::replace(&mut my_type2, Default::default()));
    println!("{:?}", my_type2); // allowed, contains default value
}
1 Like

Exactly :slight_smile:

1 Like

Also, if you have two mutable references, you can use std::mem::swap to swap their values.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.