写给前端看的Rust教程(20)WebAssembly实战二

952 阅读5分钟

原文:24 days from node.js to Rust

前言

这是我们构建真实项目的第二部分,我们的目标是构建一个运行WebAssembly的命令行工具。截止到目前为止,我们将文件路径硬编码在了程序里,并且用的还是println!()来做日志输出,代码实在是有些僵化死板。再添加更多逻辑之前,我们需要让程序更加灵活

Rust有命令行参数以及调试输出等解决方案, structopt 让命令行参数管理变得很简单,log + env_logger 会赋予你灵活的日志输出能力

正文

日志输出

许多Node.js代码库都会依赖 debug 包来进行调试输出,Rust有一个类似的,许多Rust程序都会用 log 包来做日志,用 env_logger来做输出,在这里日志和输出是解耦的

log

首先在在代码库中添加log依赖:

crates/my-lib/Cargo.toml:

[dependencies]
log = "0.4"

crates/cli/Cargo.toml:

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

log包给了我们trace!()debug!()warn!()info!()error!()五个宏,每种的用法和println!()一样,每个宏有对应的日志等级。接下来让我们试着打印一些信息来看它们是如何工作的,在my-lib包的lib.rs文件里我们添加debug!()

// 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 {})
}

cli包的main.rs文件中将println!()方法替换成更合适的方法:

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

现在我们运行代码,会发现什么也没输出

$ cargo run -p cli
[...nothing...]

env_logger

我们用env_logger将日志输出,添加如下依赖:

crates/cli/Cargo.toml:

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

然后我们在main()中进行初始化,我们紧挨着初始化代码添加了一个debug!()来确保我们可以观察到结果:

crates/cli/src/main.rs:

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

  // ...
}

不过我们运行下代码,会发现什么也没有输出

$ cargo run -p cli
[...nothing...]

这个结果是符合预期的,因为默认情况下env_logger不输出任何内容,我们需要开启相应的功能,我们可以在代码里开启也可以通过环境变量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

留意输出结果中的clilibrary的定位,你可以控制每个模块或全局的消息类型,如果你指定RUST_LOG=info参数,那么你只会看到info类型的信息

我们可以通过[package]=[level]命令来过滤每个模块的消息类型

$ 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

命令行参数处理

现在我们已经可以看到调试信息了,现在我们需要将配置信息改为从命令行获取。 clap 是一个很棒的命令行配置工具, structopt 则提供了使用clap的更简单方式

首先添加依赖

crates/cli/Cargo.toml:

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

利用structopt创建一个导出StructOpt trait的结构体:

use structopt::StructOpt;

#[derive(StructOpt)]
struct CliOptions {
}

配置StructOpt有两个层面,一个是全局,另一个是每个参数。全局配置用到结构体的structopt属性,下面这段代码给出了程序的名称、一段描述,并用到了clapAppSettings来改变颜色

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
    ]),
)]
struct CliOptions {}

结果:

cargo run -p cli -- --help
wasm-runner 0.1.0
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

添加命令行参数就像在结构中添加字段一样简单,#[structopt]属性可以决定参数的默认值、解析方式、长短格式等。下面的代码添加了一个必须的参数file_path,可以使用String类型作为文件的路径,不过structopt支持通过parse()预处理参数值将其转换为更合适的类型

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

此次外我们还需要添加一行代码,向结构体中增加from_args函数

let options = CliOptions::from_args();

现在我们的main()函数完整代码如下:

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);
        }
    }
}

我们的命令行也做了更新:

$ cargo run -p cli -- --help
wasm-runner 0.1.0
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

整合

因为我们内置的打印机制,我们可以通过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

现在我们的程序已经可以跑通了,后面我们会继续讲解实现WebAssembly的更多细节

相关阅读

总结

一个简单的日志系统就是如此,未来你可能需要将其记录到磁盘文件或日志聚合器中,在下一篇教程中我们将会介绍关于WebAssembly的更多细节

更多