Wouter
Wouter

Reputation: 2262

Lifetime issues with non-copyable owned values when trying to use them after passing them to a function

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

Answers (2)

Shepmaster
Shepmaster

Reputation: 432139

with lifetime of variables

There are no lifetimes involved here

The x variable in do_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

Florian Weimer
Florian Weimer

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

Related Questions