4

I am attempting to test a JSON bridge which I set up for use with Dart, JS and Python UI frameworks. It works fine for those, but when I attempted the same UI/logic split in a Rust program using tui-rs, I get a lifetime error when trying to deserialize a logic thread result on the UI thread.

I understand that using JSON for communication between layers both written in Rust is not the ideal way to do things, but given my goals, I hope it is understandable.

I've tried cloning which worked for things to be serialized and sent from UI to logic but this did not work for deserializing from logic to UI

use std::sync::mpsc;
use std::sync::mpsc::{Receiver, Sender};
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use serde::{Deserialize, Serialize};

#[macro_export]
macro_rules! BridgeResult {
    ($result:expr, $data:expr) => {
        BridgeResult {
            result: $result,
            data: vec![$data.to_string()],
        }
    };
}

#[derive(Serialize, Deserialize)]
struct BridgeResult {
    result: &'static str,
    data: Vec<String>,
}

#[derive(Serialize, Deserialize)]
struct App {
    state: i64,
}

impl Default for App {
    fn default() -> App {
        App { state: 0 }
    }
}

fn main() {
    let (to_logic, from_ui) = mpsc::channel();
    let (to_ui, from_logic) = mpsc::channel();

    ui(to_logic, from_logic);
    logic(to_ui, from_ui);
}

fn ui(tx: Sender<(String, String)>, rx: Receiver<(String)>) {
    let app = App::default();

    let app_string = serde_json::to_string(&app)
        .expect("failed to encode app struct for sending to logic heard");

    tx.send(("binary_switch".to_string(), app_string))
        .expect("failed to send binary_switch call and data to logic thread");
    let output_string = rx
        .recv()
        .expect("failed to get a result from logic's initialize");
    let output: BridgeResult = serde_json::from_str(&output_string)
        .expect("failed to decode result from logic's binary_switch");

    if output.result != "Ok()" {
        panic!("init failed due to: \n {:?}", output.data);
    } else {
        println!("{:?}", output.data);
    }
}

fn logic(tx: Sender<(String)>, rx: Receiver<(String, String)>) {
    loop {
        let (function, arguments) = rx
            .recv()
            .expect("failed to receive function and arguments from ui thread");
        let result = match function.as_str() {
            "binary_switch" => binary_switch(&arguments),
            _ => {
                BridgeResult! {"Err()", format!("cannot find rust function branch matching {}", function)}
            }
        };

        let output = match serde_json::to_string(&result) {
            Ok(output) => output,
            Err(_) => "{'result' : 'Err()', 'data': 'failed exit encoding!!!'}".to_string(),
        };
        tx.send(output)
            .expect("failed to send the output back to the ui thread");
    }
}

fn binary_switch(data: &String) -> BridgeResult {
    #[derive(Deserialize)]
    struct Arguments {
        state: i64,
    }

    let mut arguments: Arguments = match serde_json::from_str(&data) {
        Ok(data) => data,
        Err(err) => return BridgeResult! {"Err()", format!("failed to parse arguments\n, {}", err)},
    };

    if arguments.state == 0 {
        arguments.state += 1;
    } else {
        arguments.state -= 1;
    }

    BridgeResult! {"Ok()", arguments.state}
}

I expect this to deserialize the BridgeResult type and use the data field which should have a string 1 in it. In reality I get:

error[E0597]: `output_string` does not live long enough
  --> src/main.rs:55:53
   |
55 |     let output: BridgeResult = serde_json::from_str(&output_string)
   |                                ---------------------^^^^^^^^^^^^^^-
   |                                |                    |
   |                                |                    borrowed value does not live long enough
   |                                argument requires that `output_string` is borrowed for `'static`
...
63 | }
   | - `output_string` dropped here while still borrowed
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Please attempt harder to produce a **minimal** example next time. This could be reduced to [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ebbec9e8692367db593f210c56cc3672). – Shepmaster Jun 05 '19 at 23:44

1 Answers1

3
struct BridgeResult {
    result: &'static str,
    data: Vec<String>,
}

BridgeResult is defined to hold a result that is a static string. This is incompatible with something that is going to be deserialized from an input string. You either need result to be an owned String or something that is borrowed from the input.

Try

struct BridgeResult<'a> {
    result: &'a str,
    data: Vec<String>,
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ggriffiniii
  • 125
  • 4
  • 1
    *This is incompatible with something that is going to be deserialized from an input string* — this is incorrect or at the least imprecise. Please be careful with your wording when answering questions, otherwise people will build up incorrect mental models. – Shepmaster Jun 05 '19 at 23:47
  • 2
    Oh, I must be one of those people with an incorrect mental model. Could you elaborate on what's incorrect? How's it possible to deserialize into a &'static str? – ggriffiniii Jun 06 '19 at 13:40
  • 3
    [By starting from a `&'static str`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2a70ce90441413105af2788bdb1fa34c). – Shepmaster Jun 06 '19 at 13:57
  • 2
    Thanks. I had overlooked the case of providing a &'static str as input. – ggriffiniii Jun 06 '19 at 17:53