An easily overlooked feature of clone()
is that it can shorten the lifetimes of any references hidden inside the value being cloned. This is usually useless for immutable references, which are the only kind for which Clone
is implemented.
It would, however, be useful to be able to shorten the lifetimes of mutable references hidden inside a value. Is there something like a CloneMut
trait?
I've managed to write one. My question is whether there is a trait in the standard library that I should use instead, i.e. am I reinventing the wheel?
The rest of this question consists of details and examples.
Playground.
As a warm-up, the following is good enough when the type you're cloning is a mutable reference, not wrapped in any way:
fn clone_mut<'a, 'b: 'a>(q: &'a mut &'b mut f32) -> &'a mut f32 {
*q
}
See this question (where it is called reborrow()
) for an example caller.
A more interesting case is a user-defined mutable-reference-like type. Here's how to write a clone_mut()
function specific to a particular type:
struct Foo<'a>(&'a mut f32);
impl<'b> Foo<'b> {
fn clone_mut<'a>(self: &'a mut Foo<'b>) -> Foo<'a> {
Foo(self.0)
}
}
Here's an example caller:
fn main() {
let mut x: f32 = 3.142;
let mut p = Foo(&mut x);
{
let q = p.clone_mut();
*q.0 = 2.718;
}
println!("{:?}", *p.0)
}
Note that this won't compile unless q
gets a shorter lifetime than p
. I'd like to view that as a unit test for clone_mut()
.
When trying to write a trait that admits both the above implementations, the problem at first feels like a higher-kinded-type problem. For example, I want to write this:
trait CloneMut {
fn clone_mut<'a, 'b>(self: &'a mut Self<'b>) -> Self<'a>;
}
impl CloneMut for Foo {
fn clone_mut<'a, 'b>(self: &'a mut Self<'b>) -> Self<'a> {
Foo(self.0)
}
}
Of course that's not allowed in Rust (the Self<'a>
and Self<'b>
parts in particular). However, the problem can be worked around.
The following code compiles (using the preceding definition of Foo<'a>
) and is compatible with the caller:
trait CloneMut<'a> {
type To: 'a;
fn clone_mut(&'a mut self) -> Self::To;
}
impl<'a, 'b> CloneMut<'a> for Foo<'b> {
type To = Foo<'a>;
fn clone_mut(&'a mut self) -> Self::To {
Foo(self.0)
}
}
It's a little ugly that there is no formal relationship between Self
and Self::To
. For example, you could write an implementation of clone_mut()
that returns 77
, completely ignoring the Self
type. The following two attempts show why I think the associated type is unavoidable.
This compiles:
trait CloneMut<'a> {
fn clone_mut(&'a mut self) -> Self;
}
impl<'a> CloneMut<'a> for Foo<'a> {
fn clone_mut(&'a mut self) -> Self {
Foo(self.0)
}
}
However, it's not compatible with the caller, because it does not have two distinct lifetime variables.
error[E0502]: cannot borrow `*p.0` as immutable because `p` is also borrowed as mutable
The immutable borrow mentioned in the error message is the one in the println!()
statement, and the mutable borrow is the call to clone_mut()
. The trait constrains the two lifetimes to be the same.
This uses the same trait definition as attempt 1, but a different implementation:
trait CloneMut<'a> {
fn clone_mut(&'a mut self) -> Self;
}
impl<'a, 'b: 'a> CloneMut<'a> for Foo<'b> {
fn clone_mut(&'a mut self) -> Self {
Foo(self.0)
}
}
This doesn't even compile. The return type has the longer lifetime, and can't be made from the argument, which has the shorter lifetime.
Moving the lifetime parameter onto the method declaration gives the same error:
trait CloneMut {
fn clone_mut<'a>(&'a mut self) -> Self;
}
impl<'b> CloneMut for Foo<'b> {
fn clone_mut<'a>(&'a mut self) -> Self {
Foo(self.0)
}
}
Incidentally, notice that CloneMut<'a, To=Self>
is strictly stronger than Clone
:
impl<'a, T: 'a> CloneMut<'a> for T where T: Clone {
type To = Self;
fn clone_mut(&'a mut self) -> Self {
self.clone()
}
}
That's why I think "CloneMut
" is a good name.
The key property of &mut
references is that they are unique exclusive references.
So it's not really a clone. You can't have two exclusive references. It's a reborrow, as the source will be completely unusable as long as the "clone" is in scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With