24 days from Node.js to Rust

preface

It’s hard to imagine JavaScript without JSON, but it makes a difference for a typed language like Rust, where we need to convert JSON into objects in JavaScript, into structures in Rust

The body of the

serde

Serde (short for Serialization and Deserialization) is an amazing package that converts your data structure to JSON format with just one line of code. The package provides traits Serialize and Deserialize that let you define how to convert a data structure to JSON. It does not do the actual work of the transformation itself; the actual work is done by other packages. We’ve already used a similar package, RMP-serde, which encodes data in MessagePack format. We don’t need to know much about Serde itself, because it’s all about generic data structures, and if you want to get creative, you’ll need to implement the traits described above

With derive, Serde makes converting data very easy, and you can automatically convert most objects using Serialize and Deserialize

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Author {
    first: String,
    last: String,}Copy the code

Once you implement one or both of these traits, you have automatic support for data in any format

The following code converts one data format to JSON and MessagePack format using serde_JSON and RMP-serde

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug)]
struct Author {
    first: String,
    last: String,}fn main() {
    let mark_twain = Author {
        first: "Samuel".to_owned(),
        last: "Clemens".to_owned(),
    };

    let serialized_json = serde_json::to_string(&mark_twain).unwrap();
    println!("Serialized as JSON: {}", serialized_json);
    let serialized_mp = rmp_serde::to_vec(&mark_twain).unwrap();
    println!("Serialized as MessagePack: {:? }", serialized_mp);

    let deserialized_json: Author = serde_json::from_str(&serialized_json).unwrap();
    println!("Deserialized from JSON: {:? }", deserialized_json);
    let deserialized_mp: Author = rmp_serde::from_read_ref(&serialized_mp).unwrap();
    println!("Deserialized from MessagePack: {:? }", deserialized_mp);
}
Copy the code

Output:

$ cargo run -p day-22-serde
[snipped]
Serialized as JSON: {"first":"Samuel"."last":"Clemens"}
Serialized as MessagePack: [146, 166, 83, 97, 109, 117, 101, 108, 167, 67, 108, 101, 109, 101, 110, 115]
Deserialized from JSON: Author { first: "Samuel", last: "Clemens" }
Deserialized from MessagePack: Author { first: "Samuel", last: "Clemens" }
Copy the code

Note: Notice how the deserialization type is explicitly specified in lines 20 and 22. That’s the only way the deserializer knows what to output, right

Expand the command line program

The command line we used in the last article was very limited in what you can input, except strings. Today we’ll extend it to accept JSON data

Add serde_JSON to the Cargo. Toml file. We don’t need serde here

crates/cli/Cargo.toml

[dependencies]
my-lib = { path = ".. /my-lib" }
log = "0.4"
env_logger = "0.9"
structopt = "0.3"
rmp-serde = "0.15"
anyhow = "1.0"
serde_json = "1.0"
Copy the code

JSON

It’s good to use a custom structure when we’re sure what we want to express, but it’s not always clear, so we need an intermediary to make a more general representation. The built-in JSON representation of SERde_json can be accessed through the Serde_json ::Value enumeration without having to create a custom structure with the derive tag. This has the advantage of being able to express JSON in a more general way without knowing the exact structure

Now let’s rewrite the configuration of the command line argument to receive the path to a JSON file

crates/cli/src/main.rs

struct CliOptions {
    /// The WebAssembly file to load.
    #[structopt(parse(from_os_str))]
    pub(crate) file_path: PathBuf,

    /// The operation to invoke in the WASM file.
    #[structopt()]
    pub(crate) operation: String./// The path to the JSON data to use as input.
    #[structopt(parse(from_os_str))]
    pub(crate) json_path: PathBuf,
}
Copy the code

Now that we have a JSON file path, we need to read its contents. We read WASM files as bytes using the fs::read method. We can also read files as strings using the fs::read_to_string method

crates/cli/src/main.rs

fn run(options: CliOptions) -> anyhow::Result<String> {
    // snipped

    letjson = fs::read_to_string(options.json_path)? ;// snipped
}
Copy the code

We then parse the string into JSON using the serde_json::from_str method

crates/cli/src/main.rs

fn run(options: CliOptions) -> anyhow::Result<String> {
    // snipped

    letjson = fs::read_to_string(options.json_path)? ;letdata: serde_json::Value = serde_json::from_str(&json)? ; debug! ("Data: {:? }", data);

    // snipped
}
Copy the code

Finally, we change the data type of the return Value to serde_json::Value so that the result can be expressed as JSON

crates/cli/src/main.rs

fn run(options: CliOptions) -> anyhow::Result<serde_json::Value> {
    letmodule = Module::from_file(&options.file_path)? ; info! ("Module loaded");

    letjson = fs::read_to_string(options.json_path)? ;letdata: serde_json::Value = serde_json::from_str(&json)? ; debug! ("Data: {:? }", data);

    letbytes = rmp_serde::to_vec(&data)? ; debug! ("Running  {} with payload: {:?}", options.operation, bytes);
    let result = module.run(&options.operation, &bytes)?;
    letunpacked: serde_json::Value = rmp_serde::from_read_ref(&result)? ;Ok(unpacked)
}
Copy the code

Now we can put the input in a JSON file to run our command-line program

cargo run -p cli -- crates/my-lib/tests/test.wasm hello hello.json
[snipped]
"Hello, Potter."
Copy the code

Our command line program is getting more useful, and now we can try a more meaningful name, which we’ll rename to Wapc-Runner

We used to package it in debug mode, now let’s run it in Release mode and see what the difference is

$ cargo build --release
[snipped]
    Finished release [optimized] target(s) in 6m 08s
$ cp ./target/release/wapc-runner .
$ ./wapc-runner ./blog.wasm render ./blog.json
"The Adventures of Tom Sawyer

The Adventures of Tom Sawyer

By Mark Twain < / h2 > < p > "TOM!" \ n \ nNo answer. \ n \ n "TOM!" \n\nNo answer.\n\n "What's gone with that boy, I wonder? You TOM!" \n\nNo answer.

"
Copy the code

Release mode may take longer, depending on your machine

Now that we have a great portable WebAssembly runner, here’s a suggestion if you want to continue optimizing it: You can read JSON data directly from STDIN in the absence of JSON file parameters, and the ATTY package helps you determine if it’s running on a terminal

reading

  • serde
  • serde_json
  • rmp-serde
  • handlebars
  • The Adventures of Tom Sawyer

conclusion

We are nearing the end of this tutorial, and we will conclude with some useful packages