Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

rust - Lifetimes when Deserializing JSON within a FromForm

I'm having trouble understanding the relationship between the lifetimes on this code. Basically, I have a Rocket API that receives some x-www-form-urlencoded data, with only one key: json. This key contains, intuitively, a JSON value, encoded with percent-encoding, of a struct Message<T>.

(I'm aware this is suboptimal API design, but this is reverse-engineering work, so I have no option)

To be easily used as a request guard as From<Message<T>>, I'm implementing FromForm. To do that, I need to implement FromForm<'f> for any Message<T> where T implements Deserialize<'de>. I wrote my impl signature as impl<'f, 'de, T> FromForm<'f> for Message<T> where T: Deserialize<'de>.

To actually perform the decoding, I:

  1. Get the "json" key of the form data;
  2. URL-decode the value;
  3. Parse the JSON contained in the value.

Bailing out as soon as possible. Code doing that (explicit type annotations for the reader's convenience):

fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<Self, Self::Error> {
    // Get JSON field
    let encoded: Option<&RawStr> = items.find(|&(k, _)| k.as_str() == "json")
        .map(|(_, v)| v);
    if let None = encoded {
        return Err(MessageFormError::MissingJsonKey);
    }

    // Decode URL-string
    let decoded: Result<String, Utf8Error> = encoded.unwrap().url_decode();
    if let Err(e) = decoded {
        return Err(MessageFormError::InvalidUrl(e));
    }

    // Parse JSON
    let json: String = decoded.unwrap();
    serde_json::from_str::<Self>(&json) // Line 205
        .map_err(|e| MessageFormError::InvalidJson(e))
}

A Gist demonstrating the problem in a paste-and-run way (doesn't work on the Playground since it depends on Rocket).

As I understand:

  • The &RawStr of encoded has lifetime 'f.
  • A String is created out of it by url_decode, which lives until the end of the function
  • serde_json takes a &'x str where 'x does not need to coincide with 'de, and returns a value (so it lives to the end of the function, and since it's returned, gets moved beyond it)

But it seems my understanding is incorrect:

205 |         serde_json::from_str::<Self>(&json)
    |                                       ^^^^ does not live long enough
206 |             .map_err(|e| MessageFormError::InvalidJson(e))
207 |     }
    |     - borrowed value only lives until here
    |
note: borrowed value must be valid for the lifetime 'f as defined on the impl at 184:1...
   --> src/transport.rs:184:1
    |
184 | / impl<'f, T> FromForm<'f> for Message<T>
185 | |     where T: Deserialize<'f>
186 | | {
187 | |     type Error = MessageFormError;
...   |
207 | |     }
208 | | }
    | |_^

What am I getting wrong, and how can I return the deserialized value properly?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This section of the Serde website covers Deserialize bounds in detail.


There are two main ways to write Deserialize trait bounds, whether on an impl block or a function or anywhere else.

  • <'de, T> where T: Deserialize<'de>

    This means "T can be deserialized from some lifetime." The caller gets to decide what lifetime that is. Typically this is used when the caller also provides the data that is being deserialized from, for example in a function like serde_json::from_str. In that case the input data must also have lifetime 'de, for example it could be &'de str.

  • <T> where T: DeserializeOwned

    This means "T can be deserialized from any lifetime." The callee gets to decide what lifetime. Usually this is because the data that is being deserialized from is going to be thrown away before the function returns, so T must not be allowed to borrow from it. In your case the data is coming from URL-decoding some input, and the decoded data is thrown away after deserializing T. Another common use of this bound is functions that deserialize from an IO stream, such as serde_json::from_reader.

    To say it more technically, the DeserializeOwned trait is equivalent to the higher-rank trait bound for<'de> Deserialize<'de>. The only difference is DeserializeOwned is more intuitive to read. It means T owns all the data that gets deserialized.

Replacing your T: Deserialize<'f> bound with T: DeserializeOwned correctly communicates that T is not allowed to borrow from the URL-decoded data because the URL-decoded data will not outlive T.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...