All of these functions fail to compile. However, they fail to compile for different reasons! Can you correctly identify why each example is an error?
Composed with Rust 1.87 edition 2024. [playground]
pub fn f1<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = x;
*x;
out
}
pub fn f2<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = &mut *x;
*x;
out
}
Answer / explanation
error[E0382]: use of moved value: `x`
--> src/lib.rs
|
* | pub fn f1<'a>(x: &'a mut i32) -> &'a mut i32 {
| - move occurs because `x` has type `&mut i32`, which does not implement the `Copy` trait
* | let out = x;
| - value moved here
* | *x;
| ^^ value used here after move
error[E0503]: cannot use `*x` because it was mutably borrowed
--> src/lib.rs
|
* | pub fn f2<'a>(x: &'a mut i32) -> &'a mut i32 {
| -- lifetime `'a` defined here
* | let out = &mut *x;
| ------- `*x` is borrowed here
* | *x;
| ^^ use of borrowed `*x`
* | out
| --- returning this value requires that `*x` is borrowed for `'a`
In f1
, x
is moved from, and thus any later use of x
is invalid. In f2
, *x
is explicitly reborrowed, thus the later use of *x
would be valid in isolation, but conflicts with the even later use of that reborrow.
pub fn f3<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = std::convert::identity(x);
*x;
out
}
pub fn f4<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = std::convert::identity::<&mut _>(x);
*x;
out
}
Answer / explanation
error[E0382]: use of moved value: `x`
--> src/lib.rs
|
* | pub fn f3<'a>(x: &'a mut i32) -> &'a mut i32 {
| - move occurs because `x` has type `&mut i32`, which does not implement the `Copy` trait
* | let out = std::convert::identity(x);
| - value moved here
* | *x;
| ^^ value used here after move
error[E0503]: cannot use `*x` because it was mutably borrowed
--> src/lib.rs
|
* | pub fn f4<'a>(x: &'a mut i32) -> &'a mut i32 {
| -- lifetime `'a` defined here
* | let out = std::convert::identity::<&mut _>(x);
| - `*x` is borrowed here
* | *x;
| ^^ use of borrowed `*x`
* | out
| --- returning this value requires that `*x` is borrowed for `'a`
This one is subtle, so congratulations if you got it! std::convert::identity
is a generic fn(T) -> T
, so in identity(x)
, the compiler has to infer the generic T=&mut i32
, so no reborrow occurs. With identity::<&mut _>(x)
, however, the reference is known before inferring the function type signature, so automatic reborrowing coercion can be applied.
pub fn f5<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = x as _;
*x;
out
}
Answer / explanation
error[E0503]: cannot use `*x` because it was mutably borrowed
--> src/lib.rs
|
* | pub fn f5<'a>(x: &'a mut i32) -> &'a mut i32 {
| -- lifetime `'a` defined here
* | let out = x as _;
| - `*x` is borrowed here
* | *x;
| ^^ use of borrowed `*x`
* | out
| --- returning this value requires that `*x` is borrowed for `'a`
A reborrow occurs here because a reborrow is a coercion that occurs at coercion site, of which as
conversions are one, in addition to applying explicit casts between numeric and pointer types. This can be useful for converting concrete types into dyn
types earlier than coercion might otherwise kick in for type unification later in the code.
In fact, you could understand automatic reborrowing as being a special case of auto-deref coercion, as auto-deref applies at the same positions, since mut
auto-deref essentially desugars to &mut ****x
with one or more *
as needed (zero or more for autoref).
pub fn f6<'a>(x: &'a mut i32) -> &'a mut i32 {
let out: &mut _ = x;
*x;
out
}
Answer / explanation
error[E0503]: cannot use `*x` because it was mutably borrowed
--> src/lib.rs
|
* | pub fn f6<'a>(x: &'a mut i32) -> &'a mut i32 {
| -- lifetime `'a` defined here
* | let out: &mut _ = x;
| - `*x` is borrowed here
* | *x;
| ^^ use of borrowed `*x`
* | out
| --- returning this value requires that `*x` is borrowed for `'a`
This one's my favorite little surprise. let out = x;
moves x
, but let out: &mut _ = x;
reborrows x
. Like with f4
above, the reborrow can occur as the reference is known before creating type inference placeholders for the line.
pub fn f7<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = &mut *{ x };
*x;
out
}
pub fn f8<'a>(x: &'a mut i32) -> &'a mut i32 {
let out = { x } as _;
*x;
out
}
pub fn f9<'a>(x: &'a mut i32) -> &'a mut i32 {
let out: &mut _ = { x };
*x;
out
}
Answer / explanation
error[E0382]: use of moved value: `x`
--> src/lib.rs:3:5
|
L | pub fn f7<'a>(x: &'a mut i32) -> &'a mut i32 {
| - move occurs because `x` has type `&mut i32`, which does not implement the `Copy` trait
L | let out = &mut *{ x };
| - value moved here
L | *x;
| ^^ value used here after move
error[E0382]: use of moved value: `x`
--> src/lib.rs
|
L | pub fn f8<'a>(x: &'a mut i32) -> &'a mut i32 {
| - move occurs because `x` has type `&mut i32`, which does not implement the `Copy` trait
L | let out = { x } as _;
| - value moved here
L | *x;
| ^^ value used here after move
error[E0503]: cannot use `*x` because it was mutably borrowed
--> src/lib.rs
|
L | pub fn f9<'a>(x: &'a mut i32) -> &'a mut i32 {
| -- lifetime `'a` defined here
L | let out: &mut _ = { x };
| - `*x` is borrowed here
L | *x;
| ^^ use of borrowed `*x`
L | out
| --- returning this value requires that `*x` is borrowed for `'a`
This one's just mean, and I don't have an explanation for it, to be frank. { val }
is a code block with a tail expression val
, thus it moves from val
. f7
and f8
thus error due to use of moved value x
.
But f9
reborrows x
instead? One hypothesis is that this is a side effect of pattern binding modes being able to convert a by-move scrutinee into a by-mut pattern match. But that doesn't seem to be the case; let &mut ref mut out = { x };
moves from x
.
My only other hypothesis is that there's some shared code with temporary lifetime extension (e.g. that can extend a reborrow from borrowing the source place to having an equivalent lifetime), based on the fact that let out = { &mut 0 };
gets extended but let out = &mut *{ &mut 0 };
doesn't. (Temporary values' "containing expression" rules get weird; the tail expression of a block is not contained by that block, but by that block's containing expression. Thus the former case has the temporary lifetime contained by the let
expression, thus it can get extended to the lifetime of that let
's bindings. But the later case has the temporary contained by the outer &mut *
, and thus temporary lifetime extension does not apply, and the temporary dies at the end of the containing statement.)