3
votes

I'm working on building a REST API using Rust and Rocket. I have an endpoint at which I create a new user, defined as follows:

/// View with which to create a user
#[post("/users", format = "application/json", data = "<user_data>")]
fn create_user(user_data: Json<UserData>, db: DB) -> Status<Json<Value>> {
    let conn = db.conn();
    let _new_user_result = user_data.into_new_user(&conn);
    unimplemented!()
}

Note that there is no borrowed content here; both user_data and db are owned. Still, I get the following error on compilation:

error[E0507]: cannot move out of borrowed content
  --> src/views/user_account.rs:75:28
   |
75 |     let _new_user_result = user_data.into_new_user(&conn);
   |                            ^^^^^^^^^ cannot move out of borrowed content

For reference, the function signature of into_new_user is

fn into_new_user(self, conn: &SqliteConnection) -> Result<NewUser, Status<Json<Value>>> {
    ...
}

What is going on here? This error would be much easier to understand if I were actually borrowing anything, but given that I own everything in question, I'm baffled.

$ rustc --version; cargo --version
rustc 1.22.0-nightly (a47c9f870 2017-10-11)
cargo 0.23.0-nightly (e447ac7e9 2017-09-27)
1
Example on the rust playground: play.rust-lang.org/…coriolinus
Putting that example together suggests the answer: user_data is not an owned UserData object. It's a Json<UserData> object, where Json<T> dereferences to T. It happens that Json<T> is only used for its constructor, but the compiler doesn't know that. Now I just need to figure out how to solve this, and I'll write it up as a solution.coriolinus

1 Answers

7
votes

The issue here was with the type of user_data. Specifically, and exactly as written in the function signature, its type is Json<UserData>.

Json<T> is a wrapper type which tells Rocket how to wrap and unwrap JSON values; it's a convenience method which wraps your struct, but doesn't store any extra data. It therefore implements Deref<Target = T>, which lets you mostly ignore it. However, that only works when you don't try to consume it.

Types which implement Deref<Target=T> can generally be used as if they were a T; the compiler handles the indirection for you. However, what's actually going on in that function call is something more like <Json<UserData> as Deref<Target=UserData>>::deref().into_new_user();, and that's where the problem happens: deref is defined as fn deref(&self) -> &Self::Target. The borrow happens implicitly and invisibly, but it's there.

The Json type isn't necessary anymore in this case; it's only there to deserialize the UserData struct. Therefore, we can solve the problem by discarding it. The Json::into_inner method discards the Json wrapper, giving you an owned UserData. This can then be consumed as planned. Put together, the function should look like this:

fn create_user(user_data: Json<UserData>, db: DB) -> Status<Json<Value>> {
    let conn = db.conn();
    let _new_user_result = user_data.into_inner().into_new_user(&conn);
    unimplemented!()
}