Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Struct with partial move errors

Tags:

rust

I've got a simple struct and two instances of it as below:

#[derive(Debug)]
struct User {
    first: String,
    last: String,
    age: u32,
}

let u1 = User {
    first: String::from("John"),
    last: String::from("Doe"),
    age: 22,
};

let u2 = User {
    first: String::from("Mary"),
    ..u1
};

println!("user: {:#?}", u1);

Error message:

error[E0382]: borrow of moved value: `u1`
  --> src/main.rs:20:29
   |
15 |       let u2 = User {
   |  ______________-
16 | |         first: String::from("Mary"),
17 | |         ..u1
18 | |     };
   | |_____- value moved here
19 | 
20 |       println!("user: {:#?}", u1);
   |                               ^^ value borrowed here after partial move
   |
   = note: move occurs because `u1.last` has type `std::string::String`, which does not implement the `Copy` trait

I tried to revise it to ..&u1 hoping it will pass the borrow check so that I could spread in the base struct (u1) to u2, but to no avail, wondering is it even possible for what I wanted to do here?

I understand it's because u1.last is a String, hence needing to pass a reference, but I'm not sure how to make it work in this scenario.

like image 938
Don Klein Avatar asked Mar 04 '23 07:03

Don Klein


1 Answers

Your User type contains the type String, which owns the string data that it has (and doesn't impl Copy), which is why two users can't point to the same name in memory.

The solution you probably want:

#[derive(Debug, Clone)]
struct User {
    first: String,
    last: String,
    age: u32,
}

fn main() {
    let u1 = User {
        first: String::from("John"),
        last: String::from("Doe"),
        age: 22,
    };

    let u2 = User {
        first: String::from("Mary"),
        ..u1.clone() // Copy the strings into the new user 
        // (it also copies first, which is then thrown away? Maybe the compiler optimizes this away)
    };

    println!("user: {:#?}", u1);

}

but if you really want to have two users point to the same name in memory (pretty sure you don't), there are a couple of options:

  • You can change String to &'static str. This however means that you have to specify it when you compile. (You can't have a user type in their name at runtime, and store it in user)

    #[derive(Debug)]
    struct User {
        first: &'static str,
        last: &'static str,
        age: u32,
    }
    
    fn main() {
        let u1 = User {
            first: "John",
            last: "Doe",
            age: 22,
        };
    
        let u2 = User {
            first: "Mary",
            ..u1
        };
    
        println!("user: {:#?}", u1);
    }
    
    
    • You can use a Rc to keep track of the references to a piece of memory. This way you don't have to worry about lifetimes and who's owning what. (Comes at a tiny runtime cost though)
    use std::rc::Rc;
    
    #[derive(Debug, Clone)]
    struct User {
        first: Rc<String>,
        last: Rc<String>,
        age: u32,
    }
    
    fn main() {
        let u1 = User {
            first: Rc::new(String::from("John")),
            last: Rc::new(String::from("Doe")),
            age: 22,
        };
    
        let u2 = User {
            first: Rc::new(String::from("Mary")),
            ..u1.clone() // Clone the references, not the actual string. For strings with just a couple characters, the time difference is completely negligible)
        };
    
        println!("user: {:#?}", u1);
    }
    
    • Use a Rc<Mutex<String>> instead if you want to modify the name later, and have it change in both u1 and u2.
like image 104
8176135 Avatar answered Mar 15 '23 07:03

8176135