24 days from Node.js to Rust

preface

This is the second part of our effort to build a real project. Our goal is to build a command line tool that runs WebAssembly. So far, we’ve hardcoded the file path into our program and used println! () to do log output, the code is a bit rigid. Before adding any more logic, we need to make the program more flexible

Structopt makes it easy to manage command line parameters. Log + env_logger gives you flexible log output capabilities

The body of the

Log output

Many Node.js code libraries rely on the Debug package for debugging output. Rust has a similar example. Many Rust programs use the log package for logging and env_logger for output, where logging and output are decouped

log

First add the log dependency to the code base:

Crates/my – lib/Cargo. Toml:

[dependencies]
log = "0.4"
Copy the code

Crates/cli/Cargo. Toml:

[dependencies]
my-lib = { path = ".. /my-lib" }
log = "0.4"
Copy the code

The log package gave us trace! (), the debug! (), warn! (), info! (), the error! () five macros, each usage and println! (), each macro has a corresponding log level. Next let’s try printing some messages to see how they work. In the lib.rs file of the my-lib package we add debug! (a)

// crates/my-lib/src/lib.rs
pub fn from_file<T: AsRef<Path>>(path: T) -> Result<Self, std::io::Error> { debug! ("Loading wasm file from {:? }", path.as_ref());
    Ok(Self{})}Copy the code

Place println! In the main.rs file of the CLI package. () method is replaced with a more appropriate method:

// crates/cli/src/main.rs
match Module::from_file("./module.wasm") {
  Ok(_) => { info! ("Module loaded");
  }
  Err(e) => { error! ("Module failed to load: {}", e); }}Copy the code

Now, if we run the code, we’ll see that nothing comes out, right

$ cargo run -p cli
[...nothing...]
Copy the code

env_logger

We use env_logger to output the log, adding the following dependencies:

Crates/cli/Cargo. Toml:

[dependencies]
my-lib = { path = ".. /my-lib" }
log = "0.4"
env_logger = "0.9"
Copy the code

Then we initialize it in main(), and we add a debug right next to the initialization code! () to ensure that we can observe the results:

Crates/cli/SRC/main. Rs:

fn main() { env_logger::init(); debug! ("Initialized logger");

  // ...
}
Copy the code

But if we run the code, we’ll see that it doesn’t output anything, right

$ cargo run -p cli
[...nothing...]
Copy the code

This is as expected, because by default env_logger does not output anything. We need to enable this function, either in code or through the environment variable RUST_LOG:

$ RUST_LOG=debug cargo run -p cli
[2021-12-21T02:33:24Z DEBUG cli] Initialized logger
[2021-12-21T02:33:24Z DEBUG my_lib] Loading wasm file from "./module.wasm"
[2021-12-21T02:33:24Z INFO  cli] Module loaded
Copy the code

Note the cli and library orientation in the output. You can control the message types for each module or globally. If you specify RUST_LOG=info, you will only see info messages

We can filter the message type for each module by using the [package]=[level] command

$ RUST_LOG=cli=debug cargo run -p cli
[2021-12-21T02:35:30Z DEBUG cli] Initialized logger
[2021-12-21T02:35:30Z INFO  cli] Module loaded
Copy the code

Command line argument processing

Now that we can see the debugging information, we need to get the configuration information from the command line instead. Clap is a great command-line configuration tool, and structopt provides an easier way to use clap

First add dependencies

Crates/cli/Cargo. Toml:

[dependencies]
my-lib = { path = ".. /my-lib" }
log = "0.4"
env_logger = "0.9"
structopt = "0.3"
Copy the code

Create a structure that exports the structopt trait using structopt:

use structopt::StructOpt;

#[derive(StructOpt)]
struct CliOptions{}Copy the code

Configuring StructOpt has two layers, one global and one per parameter. The global configuration uses the structopt property of the structure. The following code gives the name of the program, a description, and uses the Clap AppSettings to change the color

use structopt::{clap::AppSettings, StructOpt};

#[derive(StructOpt)]
#[structopt(
    name = "wasm-runner",
    about = "Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/",
    global_settings(&[
      AppSettings::ColoredHelp
    ]), a)]struct CliOptions {}
Copy the code

Results:

cargo run -p cli -- --helpWasm - runner 0.1.0 from the Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/ USAGE: cli FLAGS: - h -help       Prints help information
    -V, --version    Prints version information
Copy the code

Adding command line arguments is as simple as adding fields to a structure, and the #[structopt] attribute determines the default value of the argument, how it is parsed, how it is formatted, and so on. The following code adds a required argument, file_path, that can use String as the file path, but structopt supports converting it to a more appropriate type by preprocessing the parameter values with parse()

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

This time we need to add a line of code to add the from_args function to the structure

let options = CliOptions::from_args();
Copy the code

The complete code for our main() function is now as follows:

fn main() { env_logger::init(); debug! ("Initialized logger");

    let options = CliOptions::from_args();

    match Module::from_file(&options.file_path) {
        Ok(_) => { info! ("Module loaded");
        }
        Err(e) => { error! ("Module failed to load: {}", e); }}}Copy the code

Our command line has also been updated:

$ cargo run -p cli -- --helpWasm - runner 0.1.0 from the Sample project from https://vino.dev/blog/node-to-rust-day-1-rustup/ USAGE: cli < file - path > FLAGS: -h, --help       Prints help information
    -V, --version    Prints version information

ARGS:
    <file-path>    The WebAssembly file to load
Copy the code

integration

Because of our built-in printing mechanism, we can see command-line arguments through RUST_LOG

RUST_LOG=debug ./target/debug/cli ./test_file.wasm
[2021-12-21T03:08:09Z DEBUG cli] Initialized logger
[2021-12-21T03:08:09Z DEBUG my_lib] Loading wasm file from "./test_file.wasm"
[2021-12-21T03:08:09Z INFO  cli] Module loaded
Copy the code

Now that our program is ready to run, we’ll go into more details about implementing WebAssembly later

reading

  • env_logger
  • log
  • structopt
  • clap

conclusion

A simple logging system is just that, and in the future you may need to log to a disk file or log aggregator, but we’ll cover WebAssembly in more detail in the next tutorial

More and more

  • Rust tutorial (1) From NVM to Rust
  • Rust tutorial (2) from NPM to Cargo for the front end
  • Configure Visual Studio Code in Rust (3)
  • Rust tutorial (4) Hello World
  • Rust Tutorial for the front end (5) Borrowing & Ownership
  • Part 1 of Rust Tutorial (6) String
  • Rust Tutorial (7)
  • Rust Tutorial (8)
  • Rust Tutorial (9)
  • Rust tutorial for the front end (10) from Mixins to Traits
  • Rust Tutorial (11) Module for the front-end
  • Part 2 of Rust Tutorial (12) String
  • Rust Tutorial (13) Results & Options
  • Rust tutorial (14) Errors
  • Rust Tutorial (15) closures for the front end
  • Rust Tutorial for the front-end (16) Lifecycle
  • Rust Tutorial (17) iteration for the front end
  • Rust tutorial for the front-end (18) Asynchrony
  • Part 1 of the Rust Tutorial (19) For the front end
  • Part 2 of Rust Tutorial (20) For the front End
  • Part 3 of Rust Tutorial (21) For the front End