Exchange macro. Why is this not a thing?

C++ programmer here. I miss my increments and std::exchange(). Unfortunately, Rust does not provide any, so I decided to roll my own:

macro_rules! exchange {
    ($val:expr, $new:expr) => {{
        let old = $val;
        $val = $new;
        old
    }};
}

Playground

Isn't that mem::replace?

4 Likes

Thanks! That makes more sense. However, the following not working is also why I went with a macro. Is there a trick I don't know about?

std::mem::replace(&mut my_value, my_value + 1);
cannot use `my_value` because it was mutably borrowed

How about this?

let old = my_value;
my_value += 1;

If you insist on using mem::replace, then:

let new = my_value + 1;
let old = std::mem::replace(&mut my_value, new);
1 Like

Honestly, this is a bit unfortunate because the only reason it doesn't work is the argument order. This works fine:

// Replace b with a <-> "move a into b"
fn move(a: i32, b: &mut i32) -> i32 {
    std::mem::replace(b, a)
}

Another way, hiding the aux variable inside a block expression (this is a useful pattern in general):

let old = { let new = val + 1; std::mem::replace(&mut val, new) };
3 Likes

There is swap:

I think the problem with your macro is it assumes the type is Copy. Or something (maybe the compiler is smart enough for it to work anyway though, I am not sure ).

2 Likes

Yes, that's what I discovered also by experimenting. It feels like the borrow checker could allow it, but maybe there is a good reason not to...

Yes, basically this is what the macro expands to. Maybe I'll just update my macro to use replace, as you show, and call it done.

It basically comes down to "changing execution order is a breaking change" in combination with the exclusive semantics of &mut.

That said, special functionality was carved out for method receivers ("two-phased borrows"). So this works:

pub trait ReplaceWith: Sized {
    fn replace_with(&mut self, other: Self) -> Self;
}

impl<T> ReplaceWith for T {
    fn replace_with(&mut self, other: Self) -> Self {
        std::mem::replace(self, other)
    }
}

fn main() {
    let mut x = 0;
    let y = x.replace_with(x + 1);
    println!("x {x} y {y}");
}
7 Likes

It also seems to work without a method receiver:

fn exchange(v: &mut u32) -> u32 {
    std::mem::replace(v, *v + 1)
}

fn main() {
    let mut my_value = 42;
    // std::mem::replace(&mut my_value, my_value + 1); // fails
    assert_eq!(exchange(&mut my_value), 42);
    assert_eq!(my_value, 43);
}

This came up for me recently when I was surprised that this works:

fn stuff(things: &mut Vec<Thing>) {
    for thing in std::mem::replace(things, Vec::with_capacity(things.len())) {}
}
1 Like

That's a different change -- moving the operation inside the function instead of trying to do it inline in argument position.

But it's true this also works:

    let mut my_value = 42;
    let borrow = &mut my_value;
    let y = std::mem::replace(borrow, *borrow + 1);
    assert_eq!(y, 42);
    assert_eq!(my_value, 43);

Some sort of NLL thing, implicit reborrows happen as part of the call so evaluating the first arg is a no-op I guess.

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.