LogicChains
LogicChains

Reputation: 4412

In Rust, how to push an object with a lifetime into a vector?

I've got code somewhat like the following, which attempts to read from a websocket, parse the JSON result into a struct, and push that struct onto a Vec buffer. The code however fails to compile, because the struct has a lifetime, and the borrow checker complains that the JSON string does not live long enough.

use serde::{Deserialize, Serialize};
use tungstenite::client::AutoStream;
use tungstenite::protocol::WebSocket;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct MyType<'a> {
    id: &'a str,
    count: i64,
}

fn example<'a>(
    conn: &mut WebSocket<AutoStream>,
    buff: &'a mut Vec<MyType<'a>>,
) -> Option<Box<dyn std::error::Error>> {
    match conn.read_message() {
        Err(err) => Some(err.into()),
        Ok(msg) => {
            let resp_raw = msg.to_string();
            let resp_parsed: Result<MyType<'a>, _> = serde_json::from_str(&resp_raw);
            match resp_parsed {
                Err(err) => Some(err.into()),
                Ok(resp) => {
                    buff.push(resp.clone());
                    None
                }
            }
        }
    }
}

The exact error is that borrowed value [&resp_raw] does not live long enough.

I'm wondering how I should restructure this code to satisfy the borrow checker; what is the correct way to push a struct with a lifetime onto the Vec param?

Or is it the case that the &'a str parsed into MyType actually still retains a reference to the original JSON string, so there's no way to safely do this?

Upvotes: 0

Views: 949

Answers (1)

user2722968
user2722968

Reputation: 16475

Look at serde_json::from_str carefully:

pub fn from_str<'a, T>(s: &'a str) -> Result<T> 
where
    T: Deserialize<'a>, 

This says that the T which is deserialized shares the same lifetime as the input s. This allows for zero-copy deserialization, which is what you get in MyType, where id is a reference to a string slice. This binds the lifetime of MyType to the lifetime of &resp_raw, which is local to fn example(). This will not work.

The problem can't be solved by giving buff the lifetime-parameter you've given it. The example-function owns the buffer that MyType point into. Allowing MyType to "escape" into the Vec would allow a dangling reference to be created, as the buffer is destroyed once example returns.

Change MyType to satisify DeserializeOwned, that is, take no lifetime parameter. You'll need a String or a (to safe a little memory) a Box<str> instead of a &str.

Upvotes: 1

Related Questions