2
votes

I have trouble deserializing json to struct.

Here is code that simulates my problem:

use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender };

extern crate serde_json;
extern crate serde;
#[macro_use]
extern crate serde_derive;

#[derive(Deserialize, Debug)]
struct Foo<'a> {
    a: u32,
    b: u32,
    c: &'a str,
}

fn main() {

    let (msg_tx, msg_rx): (Sender<Foo>, Receiver<Foo>) = mpsc::channel();
    {
        let js = r#"{"a":33, "b":44, "c": "ss"}"#; // initially I have a json String, here I simulate a problem
        let js_string = String::from(js);
        let f = test(js_string.as_str());

        msg_tx.send(f);
    }
}

fn test(js: &str) -> Foo {
    let foo: Foo = serde_json::from_str(js).unwrap();

    foo
}

Running that code results to the error 'js' does not live long enough. I know that changing the type of Foo c field to String will resolve the problem, but I would like to know if there is a another solution. The reason for this error is the way serde crate works in that situation, - it uses inside returned foo variable a reference to original variable, which is js_string, so it goes out of scope just after calling msg_tx.send(f); but f has a reference to it and lives longer than that scope. I'm still a rust beginner and want to master lifetime concept. I tried to fix my problem with function wrapper to set right lifetime, but failed.

1
I use crates.io/crates/rental but thinking to switch to crates.io/crates/owning_ref when I need self borrowing struct, but be aware it's a complicated subject. - Stargateur
If the channel necessarily outlives the js_string then of course you have a problem: the memory may have already been released by the time the channel tries to access it, which would result in use-after-free UB. Either you need to ensure the js_string outlives the channel (e.g. if defined in the same stack frame, as shown in the question snippet, then just move its definition to before that of the channel—local variables are dropped in reverse declaration order); or else you need to transfer ownership of the String into the channel as shown in @Yannick Funk's answer. - eggyal
@Aleksey Kanaev I edited my answer and gave you an option remaining with &str, if you think the answer is satisfactory now, accept it as best answer - Yannick Funk

1 Answers

4
votes

You have to ensure that js_string lives longer than the channel, to ensure this you can create a scope for "working" with the channel:

use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender };

extern crate serde_json;
extern crate serde;
#[macro_use]
extern crate serde_derive;

#[derive(Deserialize, Debug)]
struct Foo<'a> {
    a: u32,
    b: u32,
    c: &'a str,
}

fn main() {

    let js = r#"{"a":33, "b":44, "c": "ss"}"#;
    let js_string = String::from(js);
    let f = test(js_string.as_str());
    {
        let (msg_tx, msg_rx): (Sender<Foo>, Receiver<Foo>) = mpsc::channel();
        msg_tx.send(f);
        
    } // channel is dropped here and js_string is no longer borrowed


} // js_string is dropped here

fn test(js: &str) -> Foo {
    let foo: Foo = serde_json::from_str(js).unwrap();
    foo
}