Reputation: 2262
I'm running into some problems with lifetime of variables in Rust. The x
variable in do_stuff
is borrowed to try_wrap
and can thus not be returned in the None
case. Am I thinking about this the wrong way?
struct NonCopyable;
impl NonCopyable {
fn new() -> Self {
NonCopyable
}
}
fn do_stuff() -> NonCopyable {
let x = NonCopyable::new();
match try_wrap(x) {
Some(val) => val,
None => x,
}
}
fn try_wrap(x: NonCopyable) -> Option<NonCopyable> {
None
}
fn main() {}
error[E0382]: use of moved value: `x`
--> src/main.rs:13:17
|
11 | match try_wrap(x) {
| - value moved here
12 | Some(val) => val,
13 | None => x,
| ^ value used here after move
|
= note: move occurs because `x` has type `NonCopyable`, which does not implement the `Copy` trait
Upvotes: 2
Views: 73
Reputation: 432139
with lifetime of variables
There are no lifetimes involved here
The
x
variable indo_stuff
is borrowed
No, it is not.
Am I thinking about this the wrong way?
Yes. Borrowing is indicated by an ampersand &
and/or a lifetime parameter 'foo
:
&i32 // a borrowed integer
&'a str // a borrowed string slice with a lifetime
Foo<'b> // a type that contains a borrow of some kind
Your try_wrap
function takes ownership of x
:
fn try_wrap(x: NonCopyable) -> Option<NonCopyable>
That means x
is gone and the calling function cannot access it anymore. It has been moved into try_wrap
, which is now free to do whatever it wants with the value, including destroy it. That is why the calling function is no longer able to safely access it and why you get the error.
If the type implemented Copy
, the compiler would have instead implicitly created a copy of the value and passed it in. If the type implemented Clone
, you could have explicitly called .clone()
on the argument to try_wrap
in order to keep the local value.
As Florian Weimer notes, you can use a type to return either the wrapped value or the original. It's difficult to tell based on your example, but I disagree with using Result
unless it's an error. Instead, I'd create my own one-off enum or use something like Either
:
extern crate either;
use either::Either;
fn do_stuff() -> NonCopyable {
let x = NonCopyable::new();
match try_wrap(x) {
Either::Left(val) => val,
Either::Right(x) => x,
}
}
fn try_wrap(x: NonCopyable) -> Either<NonCopyable, NonCopyable> {
Either::Right(x)
}
You could also embed the logic of try_wrap
back into do_stuff
, or split up try_wrap
so that the logic doesn't require ownership:
fn do_stuff() -> NonCopyable {
let x = NonCopyable::new();
if should_wrap(&x) { do_wrap(x) } else { x }
}
fn should_wrap(x: &NonCopyable) -> bool { false }
fn do_wrap(x: NonCopyable) -> NonCopyable { x }
Since you are returning the same type, it's also possible you would want to take a mutable reference to the value and just do whatever conditional changes need to happen:
fn do_stuff() -> NonCopyable {
let mut x = NonCopyable::new();
try_wrap(&mut x);
x
}
fn try_wrap(x: &mut NonCopyable) {}
Upvotes: 2
Reputation: 33747
I think Option<NonCopyable>
is simply the wrong return type for try_wrap
. You need a two-armed sum type here like Result
, so that the caller can recover the argument in case of an error, perhaps like this:
fn do_stuff() -> NonCopyable {
let x = NonCopyable::new();
match try_wrap(x) {
Ok(val) => val,
Err(x) => x,
}
}
fn try_wrap(x: NonCopyable) -> Result<NonCopyable, NonCopyable> {
Err(x)
}
Upvotes: 1