rust-signals icon indicating copy to clipboard operation
rust-signals copied to clipboard

Add replace_object_with that can pass an object without using mutable…

Open adamritter opened this issue 3 years ago • 6 comments

… reference. This makes it possible to change an object in place and reuse it.

adamritter avatar Oct 30 '22 13:10 adamritter

Is there any advantage to this instead of replace_with?

Also, if you want to reuse an existing object, you can just use lock_mut:

// lock is a &mut reference, so you can change the object however you want
let mut lock = some_mutable.lock_mut();

lock.foo = 5;

*lock = ...

std::mem::replace(&mut lock, ...);

Pauan avatar Oct 31 '22 17:10 Pauan

There are multiple advantages:

one advantage is that it makes the function clearer to understand and harder to misuse than replace_with: when I was reading the todo list implementation, I wasn't sure why a mutable reference is taken as an input and an object is returned.

What happens if I change the object in the reference? What's the difference between changing that and returning an object? Without reading the source code of the function, these things are not clear.

Also with replace_object_with one can for example change an element of a vector without cloning the whole vector.

replace_object_with(|c| c+1) is clearer to read and understand than replace_with(|c| *c+1)

adamritter avatar Nov 01 '22 12:11 adamritter

What happens if I change the object in the reference?

Nothing happens, you are replacing the existing value with a new value. The old value is discarded, that's how replace_with works.

The type and behavior of Mutable::replace_with is exactly the same as RefCell::replace_with. I tried to keep the API consistent with the Rust stdlib.

I wasn't sure why a mutable reference is taken as an input and an object is returned

It cannot give a value, because if the closure panics then it will cause undefined behavior. So instead it gives a reference to the value. And giving a &mut reference is more useful than giving a & reference.

What your code does is... it takes the value out of the Mutable and replaces it with a new default value, and then it replaces the default value with the new value. So it's doing 2 replacements instead of 1.

Also with replace_object_with one can for example change an element of a vector without cloning the whole vector.

You can also do that with lock_mut, which is cleaner and more efficient:

let mut lock = some_mutable.lock_mut();
lock[1] = ...

Or even better yet, use MutableVec which is much more efficient.

You should not be using replace_with to change an existing value, it is only for replacing the old value with an entirely new value. If you want to change the existing value, use lock_mut.

replace_object_with(|c| c+1) is clearer to read and understand than replace_with(|c| *c+1)

I don't think it's easier to understand, because it is swapping the value with the default value and then swapping it again with the new value.

But replace_with just swaps it with the new value, which is more intuitive and faster. Or you can use lock_mut which doesn't do any swaps at all.

Pauan avatar Nov 01 '22 13:11 Pauan

I see, thanks!

On Tue, Nov 1, 2022, 13:34 Pauan @.***> wrote:

What happens if I change the object in the reference?

Nothing happens, you are replacing the existing value with a new value. The old value is discarded, that's how replace_with works.

The type and behavior of Mutable::replace_with is exactly the same as RefCell::replace_with https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.replace_with. I tried to keep the API consistent with the Rust stdlib.

I wasn't sure why a mutable reference is taken as an input and an object is returned

It cannot give a value, because if the closure panics then it will cause undefined behavior. So instead it gives a reference to the value. And giving a &mut reference is more useful than giving a & reference.

What your code does is... it takes the value out of the Mutable and replaces it with a new default value, and then it replaces the default value with the new value. So it's doing 2 replacements instead of 1.

Also with replace_object_with one can for example change an element of a vector without cloning the whole vector.

You can also do that with lock_mut, which is cleaner and more efficient:

let mut lock = some_mutable.lock_mut(); lock[1] = ...

Or even better yet, use MutableVec which is much more efficient.

You should not be using replace_with to change an existing value, it is only for replacing the old value with an entirely new value. If you want to change the existing value, use lock_mut.

replace_object_with(|c| c+1) is clearer to read and understand than replace_with(|c| *c+1)

I don't think it's easier to understand, because it is swapping the value with the default value and then swapping it again with the new value.

But replace_with just swaps it with the new value, which is more intuitive and faster. Or you can use lock_mut which doesn't do any swaps at all.

— Reply to this email directly, view it on GitHub https://github.com/Pauan/rust-signals/pull/62#issuecomment-1298518219, or unsubscribe https://github.com/notifications/unsubscribe-auth/AN5SWAG7QUVWOOMLMNNWEJLWGEL6VANCNFSM6AAAAAARSI7YFM . You are receiving this because you authored the thread.Message ID: @.***>

adamritter avatar Nov 01 '22 13:11 adamritter

I thought that there's no difference in speed because the value that is set is overwritten in the same function and the compiler can guarantee that the called function cannot read the default value, but I didn't do benchmarks.

On Tue, Nov 1, 2022 at 1:36 PM Adam Ritter @.***> wrote:

I see, thanks!

On Tue, Nov 1, 2022, 13:34 Pauan @.***> wrote:

What happens if I change the object in the reference?

Nothing happens, you are replacing the existing value with a new value. The old value is discarded, that's how replace_with works.

The type and behavior of Mutable::replace_with is exactly the same as RefCell::replace_with https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.replace_with. I tried to keep the API consistent with the Rust stdlib.

I wasn't sure why a mutable reference is taken as an input and an object is returned

It cannot give a value, because if the closure panics then it will cause undefined behavior. So instead it gives a reference to the value. And giving a &mut reference is more useful than giving a & reference.

What your code does is... it takes the value out of the Mutable and replaces it with a new default value, and then it replaces the default value with the new value. So it's doing 2 replacements instead of 1.

Also with replace_object_with one can for example change an element of a vector without cloning the whole vector.

You can also do that with lock_mut, which is cleaner and more efficient:

let mut lock = some_mutable.lock_mut(); lock[1] = ...

Or even better yet, use MutableVec which is much more efficient.

You should not be using replace_with to change an existing value, it is only for replacing the old value with an entirely new value. If you want to change the existing value, use lock_mut.

replace_object_with(|c| c+1) is clearer to read and understand than replace_with(|c| *c+1)

I don't think it's easier to understand, because it is swapping the value with the default value and then swapping it again with the new value.

But replace_with just swaps it with the new value, which is more intuitive and faster. Or you can use lock_mut which doesn't do any swaps at all.

— Reply to this email directly, view it on GitHub https://github.com/Pauan/rust-signals/pull/62#issuecomment-1298518219, or unsubscribe https://github.com/notifications/unsubscribe-auth/AN5SWAG7QUVWOOMLMNNWEJLWGEL6VANCNFSM6AAAAAARSI7YFM . You are receiving this because you authored the thread.Message ID: @.***>

adamritter avatar Nov 01 '22 13:11 adamritter

I thought that there's no difference in speed because the value that is set is overwritten in the same function and the compiler can guarantee that the called function cannot read the default value, but I didn't do benchmarks.

That depends on the optimizer. Because Default::default runs arbitrary user code, it can potentially have side effects, so it may not be able to optimize it away.

Pauan avatar Nov 02 '22 22:11 Pauan