2

If ownership from one struct is (partially) transferred to another struct (e.g. by copying part of its fields), how can it be "transferred back", as in how can I drop the former struct?

Consider this code:

struct User {
    active: bool,
    username: String
}

fn main() {
    let user1 = User { // create some user
        username: String::from("someusername123"),
        active: true,
    };

    let user2 = User { // create some other user. Copy some date from user1.
        active: false,
        ..user1 // Now ownership has been partially moved from user1 to user2
    };

    drop(user2); // we first drop user2. Now, ownership should be moved back to user1?
    drop(user1); // we want to delete user1. But ownership apparently hasn't been moved back. Error: use of partially moved value: `user1`
}

The last drop fails but it wouldn't if user2 had not copied some field from user1.

It works well if user2 copies Copy-able data from user1, like bool, because then only data in the stack gets copied to the best of my knowledge, but we get an issue if user2 copies a String from user1.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
black
  • 1,151
  • 3
  • 18
  • 46
  • 2
    That's not transferring ownership. That's making a new copy of the boolean in the second struct instance, because `bool` is a Copy type. What are you actually trying to accomplish? – PitaJ Sep 20 '22 at 17:50
  • If you do this with the `username: String` field instead, it does come up with some "partial move" errors. Is that what you're referring to? – PitaJ Sep 20 '22 at 17:52
  • I can compile and run your code without getting an error. What's the issue? – Andrew Sep 20 '22 at 17:54
  • Ownership is about data and control. You can delegate control over data with a unique `&mut` reference, which will automatically return control when the owner of the `&mut` reference is dropped. – PitaJ Sep 20 '22 at 17:57
  • Sorry for the confusion, I meant to copy the String, as you suggested! – black Sep 20 '22 at 18:00
  • @PitaJ Thanks, I see... Is that possible with the `..user1` syntax? To make sure that contorl gets delegated back once user2 is dropped? – black Sep 20 '22 at 18:01
  • 3
    What you are describing, that you "temporarily move ownership to something else and then transfer it back once the object drops" is exactly what *borrowing* does. The wording isn't exactly correct, it's technically not a 'moving ownership', but for the most part, a mutable borrow behaves exactly like what you describe. – Finomnis Sep 20 '22 at 18:03
  • No, unfortunately that syntax can only be used with structs of the same type. You'll have to do each field manually. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d8c50a8e151dd3bfe4a7f9e9aa2337ce – PitaJ Sep 20 '22 at 18:31
  • I would say expecting `drop` to transfer ownership is exposing a huge misunderstanding of ownership. You can *only* drop something you explicitly own. You cannot drop something you do not own. – tadman Sep 20 '22 at 20:38

1 Answers1

4

Just to clarify, the ..user1 syntax is not copy or clone, but move. The ownership is transferred. With bool or other primitive types, they implement Copy, which is why when transferring ownership, they clone their data instead of being moved

In Rust, you cannot transfer the ownership of something and automatically have it move back. It is always the responsibility of the owner to move itself to somewhere else.

In your code example, when you initialize user2, the ownership of username is transferred, so user1.username is no longer accessible. Thus from now on, you no longer have access to the entire user1, which is why you got the error value used here after partial move when trying to move its entirety to drop(). And it is now the responsibility of user2 to move the username it owns back to user1 before user2 is dropped.

If you want to temporarily let user2 have access to what user1 holds, what you need is borrow.

struct User {
    active: bool,
    username: String
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123".to_string(),
    };
    
    let user_ref = &user1;  // user_ref borrows user1, but user1 still owns the data
    
    drop(user_ref);     // Here only the borrow is dropped
    drop(user1);        // Here the data is moved into drop()  
}

If you also want to distinguish user_ref from user1, you can create a separate type that is a reference of User type

struct User {
    active: bool,
    username: String
}

struct UserRef<'a> {
    active: bool,
    username: &'a String,
}
impl User {
    fn as_user_ref<'a>(&'a self) -> UserRef<'a> {
        UserRef {
            active: false,
            username: &self.username,
        }
    }
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123".to_string(),
    };
    
    let user_ref = user1.as_user_ref();  // user_ref is type UserRef<'a>
    
    drop(user_ref);     // Here UserRef is dropped, but because it holds &String,
                        // nothing happens to the data
    
    drop(user1);        // Here the data is moved into drop()  
}

Or if what you want is just to copy user1, and the two variables are not related, just derive the Clone trait.

#[derive(Clone)]
struct User {
    active: bool,
    username: String
}
impl User {
    fn clone_inactive(&self) -> User {
        let mut user = self.clone();
        user.active = false;
        user
    }
}

fn main() {
    let user1 = User {
        active: true,
        username: "someusername123".to_string(),
    };
    
    let user2 = user1.clone_inactive(); // user1 is cloned, there are now 2 String
    
    drop(user2);     // The String owned by user2 is dropped
    drop(user1);     // The String owned by user1 is dropped
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
YthanZhang
  • 356
  • 1
  • 6