Move occurs because `*row` has type ... which does not implement the `Copy` trait

I have a MySQL database to store information about my cats. One table holds all the known races, another table holds all the known colors. Than a final table to save the name and age of my cat and a foreign key to the races- and colors-table matching the right race/color of the cat.

Now I have written a function to return all the cats in the database in a vector. To do that I first need to retrieve the two foreign keys needed to get the right color/race.

I thought I got close until Rust spitted out a ton of errors. This is my code:

    fn read(&mut self) -> &std::vec::Vec<Cat> {
        let selected_cat_rows : std::vec::Vec<std::result::Result<mysql::Row, mysql::Error>> = 
            self.conn.prep_exec("SELECT name, age, color_id, race_id FROM cats", ()).unwrap().collect();
        let mut cats : std::vec::Vec<Cat> = std::vec::Vec::<Cat>::new();

        for row in &selected_cat_rows {
            //Retrieve color name from color-table by color_id
            let color_id : u64 = row.unwrap().take("color_id").unwrap();
            let mut color : std::vec::Vec<String> = std::vec::Vec::<String>::new();
            let color_rows : std::vec::Vec<std::result::Result<mysql::Row, mysql::Error>> = self.conn.prep_exec("SELECT color_name FROM colors WHERE id = :color_id", params!("color_id" => color_id)).unwrap().collect();
            for subrow in &color_rows {
                color.push(subrow.unwrap().take("color_name").unwrap());
            }

            //Retrieve race name from race-table by race_id
            let race_id : u64 = row.unwrap().take("race_id").unwrap();
            let mut race : std::vec::Vec<String> = std::vec::Vec::<String>::new();
            let race_rows : std::vec::Vec<std::result::Result<mysql::Row, mysql::Error>> = self.conn.prep_exec("SELECT race_name FROM races WHERE id = :race_id", params!("race_id" => race_id)).unwrap().collect();
            for subrow in &race_rows {
                color.push(subrow.unwrap().take("race_name").unwrap());
            }

            let cat : Cat = Cat {
                name: row.unwrap().take("name").unwrap(),
                age: row.unwrap().take("age").unwrap(),
                color: color[0].parse().unwrap(),
                race: race[0].parse().unwrap(),
            };

            cats.push(cat);
        }

        &cats
    }

All the errors:

error[E0507]: cannot move out of `*row` which is behind a shared reference
   --> src/main.rs:437:38
    |
437 |                 let color_id : u64 = row.unwrap().take("color_id").unwrap();
    |                                      ^^^
    |                                      |
    |                                      move occurs because `*row` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                                      help: consider borrowing the `Result`'s content: `row.as_ref()`

error[E0507]: cannot move out of `*subrow` which is behind a shared reference
   --> src/main.rs:441:32
    |
441 |                     color.push(subrow.unwrap().take("color_name").unwrap());
    |                                ^^^^^^
    |                                |
    |                                move occurs because `*subrow` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                                help: consider borrowing the `Result`'s content: `subrow.as_ref()`

error[E0507]: cannot move out of `*row` which is behind a shared reference
   --> src/main.rs:445:37
    |
445 |                 let race_id : u64 = row.unwrap().take("race_id").unwrap();
    |                                     ^^^
    |                                     |
    |                                     move occurs because `*row` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                                     help: consider borrowing the `Result`'s content: `row.as_ref()`

error[E0507]: cannot move out of `*subrow` which is behind a shared reference
   --> src/main.rs:449:32
    |
449 |                     color.push(subrow.unwrap().take("race_name").unwrap());
    |                                ^^^^^^
    |                                |
    |                                move occurs because `*subrow` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                                help: consider borrowing the `Result`'s content: `subrow.as_ref()`

error[E0507]: cannot move out of `*row` which is behind a shared reference
   --> src/main.rs:453:27
    |
453 |                     name: row.unwrap().take("name").unwrap(),
    |                           ^^^
    |                           |
    |                           move occurs because `*row` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                           help: consider borrowing the `Result`'s content: `row.as_ref()`

error[E0507]: cannot move out of `*row` which is behind a shared reference
   --> src/main.rs:454:26
    |
454 |                     age: row.unwrap().take("age").unwrap(),
    |                          ^^^
    |                          |
    |                          move occurs because `*row` has type `std::result::Result<mysql::Row, mysql::Error>`, which does not implement the `Copy` trait
    |                          help: consider borrowing the `Result`'s content: `row.as_ref()`

error[E0515]: cannot return reference to local variable `cats`
   --> src/main.rs:462:13
    |
462 |             &cats
    |             ^^^^^ returns a reference to data owned by the current function

error: aborting due to 7 previous errors

Some errors have detailed explanations: E0507, E0515.
For more information about an error, try `rustc --explain E0507`.
error: could not compile `cat_generator`.

It are the typical Rust-errors like 'move happened', 'borrow result', 'No copy trait implemented'...
But I don't get it...

For example this piece of code let color_id : u64 = row.unwrap().take("color_id").unwrap(); returns those kind of errors. row is just the variable from the foreach-loop... Why does using that variable give an error?

What causes these errors and how to fix it?

Did you try to follow the advice provided in the error message?

help: consider borrowing the Result's content: row.as_ref()

For an explanation of what is happening: you have a vector of results and each result can be either a MySQL row or an error. You then iterate the vector, which borrows each element in turn. Calling Result::unwrap() on &Result<mysql::Row, mysql::Error> must first dereference the borrow. Before we go any further, let's take a close look at one of the lines the error appears on:

let color_id : u64 = row.unwrap().take("color_id").unwrap();

Of course, we know the type of row, since error message already described it. This assignment operator is trying to copy the value on the RHS to color_id. But it cannot because, as the error message is pretty clear in stating, mysql::Row does not implement Copy! But why does this assignment want to copy the data in the first place?

Now we have to talk about Rust's ownership model, and more specifically "move semantics" vs "copy semantics". By default, Rust will choose move semantics for assignments, meaning that ownership will be transferred from the RHS to the LHS. There are cases where move semantics would not be appropriate, such as when dereferencing a borrow. You don't want to invalidate the borrowed data, that would be a catastrophic bug! In these cases, Rust will use copy semantics.

Copy is exactly what it sounds like; data is copied from one location in memory (e.g. through a borrowed reference) to another; the assignment destination. In some cases, this can literally just be a call to memcpy, but that detail doesn't matter for this conversation.


So that explains why you get the error. How to fix it is a bit more challenging, in part because the types are complex, and you must be familiar with the APIs and Rust's ownership rules. I'll try my best to guide you to a solution.

To start with, let's look at the definition of Result::unwrap(). It's important to see this for yourself, even if it doesn't seem relevant right now...

pub fn unwrap(self) -> T

This method signature says that calling this function will consume self and return an owned value (of type T). "Consume self" is another way of saying "transfer ownership of self". In other words, the Result type will be unusable from here on out after this method returns. But wait! Isn't the Result type borrowed in this case? That would be bad. The compiler would error out saying "cannot move out of borrow". But it also gave you a hint earlier. Remember this?

help: consider borrowing the Result's content: row.as_ref()

Let's look at Result::as_ref()!

pub fn as_ref(&self) -> Result<&T, &E>

:laughing: Well, it returns another Result, but this time the T type is borrowed as &T. That sounds like the thing we want.

Now let's move on to mysql::Row::take()

pub fn take<T, I>(&mut self, index: I) -> Option<T>

This mutably borrows self and returns an owned type T. But wait! Isn't row already immutably borrowed? This also cannot work. But there's another option:

pub fn get<T, I>(&self, index: I) -> Option<T>

Now we can safely call this method and get back an owned type T. In this case, T is u64, which implements Copy! Hooray! So we don't have to do any more borrow juggling, we can just copy it to the LHS and call it a day.


Putting it all together, here's what I've come up with:

let color_id: u64 = row.as_ref().unwrap().get("color_id").unwrap();

Now, it should be noted that I don't actually have a full copy of your code, so I have no way of formally testing this. All I have to go on is documentation and some basic understanding of Rust's ownership model. If this works for you, then great! If not, well, sorry. I did try my best. :slight_smile:

This is kind of a long reply, but sometimes it takes a lot to actually explain what a Rust compiler error really means. The messages themselves are actually quite good, and can be very helpful for guiding you to something that works. That's why I quoted the help part of the error message twice. I really want this point to sink in so that you can become as efficient as possible with the language.

3 Likes

Thanks a lot for your reply. I understand it a lot better but not yet fully. This part of Rust (copy/borrow/...) is really the most complex, vague, most frustrating and hardest part of the learning curve for a beginner in my opinion... I'll read into it more.

Your solution worked by the Way! Thanks!
I tried as_ref() when the compiler suggested it, but it didn't told me to change take() in get() which still caused an error.

Glad to help!

Yes, and this is where things can get tricky. I learned early on when working with C that the compiler cannot always show you every error in the code. Sometimes fixing one error will just create a new one. A lot of that has to do with the fact that fixing errors changes the code that must be compiled. That just makes these things impossible to predict.

Just stick with it, be persistent, and always remember the Rust koans.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.