What does the "value used here after move" error mean in Rust?

Asked

Viewed 124 times

4

I am studying Rust and would like to better understand when I can and when I can’t use the same variable more than once in the same scope.

I leave below two examples.

In the first, the program works as expected, but in the second, which does the same thing, I come across the error value used here after move. Behold:

use regex::Regex;

fn main() {
    let text = "Whatever your hand finds to do, do it with all your might, for in the realm of the dead, where you are going, there is neither working nor planning nor knowledge nor wisdom.";
    let re = Regex::new(r"\b(m|d)[a-z]+").unwrap();
    let captures = re.find_iter(text);
    for cap in captures {
        let begin = cap.start() - 10;
        let fim = cap.end() + 10;
        println!("{}", &text[begin..fim])
    }
}

Works correctly. No error.

On the other hand, the following programme:

use fancy_regex::Regex;

fn main() {
    let text = "Whatever your hand finds to do, do it with all your might, for in the realm of the dead, where you are going, there is neither working nor planning nor knowledge nor wisdom.";
    let re = Regex::new(r"\b(m|d)[a-z]+").unwrap();
    let captures = re.find_iter(text);
    for cap in captures {
        let begin = cap.unwrap().start() - 10;
        let fim = cap.unwrap().end() + 10;
        println!("{}", &text[begin..fim])
    }
}

Returns the error:

error[E0382]: use of moved value: `cap`
   --> src/main.rs:9:19
    |
7   |     for cap in captures {
    |         --- move occurs because `cap` has type `std::result::Result<fancy_regex::Match<'_>, fancy_regex::Error>`, which does not implement the `Copy` trait
8   |         let begin = cap.unwrap().start() - 10;
    |                         -------- `cap` moved due to this method call
9   |         let fim = cap.unwrap().end() + 10;
    |                   ^^^ value used here after move

My question is: why in the first case I can use the variable cap twice and not the second time? What does error mean value used here after move?

I am deliberately using two different modules to illustrate the problem.

1 answer

5


Rust is an extremely different language from the others mainstream, And that differential factor is precisely what this question is about. Although somewhat "strange", this factor is also seen as one of Rust’s strongest points. Because of this sui generi, it is impossible to write a fully detailed answer, since it would have to be explained in full as the model of Ownership works in Rust.

Therefore, for a full understanding, it is ideal to read, in full, the chapter Understanding Ownership, from the official language book.

That said, a certain summary won’t hurt.


The system of Ownership in Rust is governed over three rules:

  1. Every value in Rust has one associated variable. This variable is called owner of its value.
  2. Each value can have only one owner. Heed
  3. When the variable (owner of a value) leaves the scope of a block, the value will be released.

Let’s look at the second item above. Although a value can have only one owner at a given time, there is no impediment to it transfer the Ownership of that value. That is, it is possible transfer possession (Ownership) value from one variable to another. This type of action is called move (the Ownership).

For example:

// A variável `s1` é dona da String:
let s1 = String::from("hello");

// A String foi movida da variável `s1`.
// A partir de agora, a variável `s2` é a dona.
let s2 = s1;

In this sense, we can reproduce an error similar to the question when trying to access the variable value s1 after being moved to s2. If we tried to do this:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1);
}

The compiler would launch an error similar to this:

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:28
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 | 
5 |     println!("{}, world!", s1);
  |                            ^^ value borrowed here after move

This basically means that we try to access the value of a variable that no longer owns it. Obviously, as rule 2 postulates (a value can only have an owner), this would be a reason for error. If the value had its possession changed between variables, it is expected that the former owner is no longer valid - which justifies the error.

Thus, we understand that we cannot use a variable whose value was moved to try access a value.


But the value nay was moved in the question code... or was? (...)

Retaking the code problematic of the AP:

use fancy_regex::Regex;

fn main() {
    let text = "...";
    let re = Regex::new(r"\b(m|d)[a-z]+").unwrap();
    let captures = re.find_iter(text);
    for cap in captures {
        let begin = cap.unwrap().start() - 10;
        let fim = cap.unwrap().end() + 10;
        println!("{}", &text[begin..fim]);
    }
}

It is possible to notice (by the error message of the question) that the error occurs in view that the value of the variable cap is trying to be accessed after being moved.

In the situation in question, cap is the type std::result::Result<Match, Error>. Let’s see now the signature of the method unwrap:

pub fn unwrap(self) -> T

Note that the value self is being used so as to take the Ownership of the instance. This means that a modification of Ownership implicit. Pay attention to self - if it were &self or &mut self, the story would be different; but we will not deal with it here not to extend much.

That is, the method unwrap of Results take for themselves the possession of the value, which is "totally consumed". This means that the variable that contained the Result can no longer be "read", since the value had its ownership changed. Think that the possession was for a variable within the method unwrap (this variable is the parameter self).

Therefore, to correct this, just call unwrap only once, so the variable is no longer accessed later:

let re_match = cap.unwrap();
let begin = re_match.start() - 10;
let fim = re_match.end() + 10;
// ...

Note that now the variable cap, single for each iteration of the for, is no longer used after possession of its value has been moved to the parameter self of the method unwrap.

According to chapter 5.3, [Struct] Method Syntax:

Having a method that takes Ownership of the instance by using just self as the first Parameter is Rare; this technique is usually used when the method Transforms self into Something Else and you want to Prevent the Caller from using the original instance after the Transformation.

I mean, a method that takes the Ownership of the instance (using self as first argument) is rare. This technique is usually used when the method transforms self on something else and there is the desire to prevent the caller from using the original instance after the transformation.

And this need makes sense in the case of unwrap, so that the Ownership of the instance of Result is taken after calling the method in question.


Again, I insist that the reading of the chapters mentioned in this answer are of paramount importance so that really is understood as the system of Ownership works in Rust. What I said here is just a summary. I didn’t even mention anything about borrowing, which is also important in this field.

  • 1

    I know I’m not supposed to use the commentary for this, but congratulations on the answer!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.