• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

WebAssembly运行时库(WASM runtimewasmer 或 wasmtime)\rust官方demo猜数字编译为WASI目标并使用Wasmer运行

武飞扬头像
西京刀客
帮助1

WebAssembly运行时库(wasmer 或 wasmtime、wasmer-go)

一、引子

陈天 · Rust 编程第一课
https://time.geekbang.org/column/article/468066

插件(微内核)结构
插件(微内核)结构插件结构(Plugin Architecture)也被称为微内核结构(Microkernel Architecture),它可以让你的系统拥有一个足够小的核心,然后围绕着这个核心以插件的方式注入新的功能。我们平时使用的 VS Code 就是典型的插件结构。它的核心功能就是文本的编辑,但通过各种插件,它可以支持代码的语法高亮、错误检查、格式化等等功能。在构建插件结构时,我们需要设计一套足够稳定的接口,保证插件和核心之间的交互;还需要设计一套注册机制,让插件可以被注册进系统,或者从系统中删除。在 Rust 下,除了正常使用 trait 和 trait object 来构建插件机制,在系统内部使用插件结构外,还可以通过 WebAssembly(通过 wasmer 或 wasmtime) 或者 rhai 这样的嵌入式脚本来允许第三方通过插件来扩展系统的能力:
学新通

通过 WebAssembly(通过 wasmer 或 wasmtime) 或者 rhai 这样的嵌入式脚本来允许第三方通过插件来扩展系统的能力!

WebAssembly 简称 Wasm,承诺带来闪电般的启动速度以及极低的占用空间,这也使其成为无服务器架构与物联网边缘设备的理想选择。

1. 什么是WASI

WebAssembly 的 “Web” 表明了 Wasm 的出身,也就是说它发明并最早应用于 Web 浏览器中, “Assembly” 则表明了 Wasm 的本质,这个词翻译过来的意思是 “汇编”,也就是指代它的 V-ISA 属性。

鉴于 Wasm 所拥有“可移植”、“安全”及“高效”等特性,Wasm 也被逐渐应用在 Web 领域之外的一些其他场景中。今天我们将要讲解的,便是可以用于将 Wasm 应用到 out-of-web 环境中的一项新的标准 —— WASI(WebAssembly System Interface,Wasm 操作系统接口)。通过这项标准,Wasm 将可以直接与操作系统打交道

WASM(Web Assembly)尽管是为了提高网页中性能敏感模块表现而提出的字节码标准, 但是WASM却不仅能用在浏览器(broswer)中, 也可以用在其他环境中. 在这些环境中, 我们则需要支持WASI(WebAssembly System Interface, WASM系统接口)的runtime来执行我们编译运行的wasm模块.

WASI是一个新的API体系, 由Wasmtime项目设计, 目的是为WASM设计一套引擎无关(engine-indepent),
面向非Web系统(non-Web system-oriented)的API标准. 目前, WASI核心API(WASI Core)在做覆盖文件,
网络等等模块的API,

wasmer 与 Wasmtime.这两款运行时都号称支持了WASI标准。

2. 有哪些优秀的 WebAssembly 运行时?

有哪些优秀的 WebAssembly 编译器与运行时?
参考URL: https://time.geekbang.org/column/article/292041

目前业界有哪些优秀的 Wasm 运行时。这些运行时都是 Wasm 可以在 out-of-web 领域大显身手的最基本保障。它们其中有些支持 WASI 抽象系统接口,有些支持 Wasm Post-MVP 标准中的部分提案,还有一些可以被专门用在诸如嵌入式、IOT 物联网以及甚至云,AI 和区块链等特殊的领域和场景中。

Wasm 虽然“出身”于 Web,但实际上却也可以 out-of-web,根据上节引子,我们接下来关注研究 wasmer 与 Wasmtime。

二、wasmer

1. 什么是wasmer

官网:https://wasmer.io/
github官网:https://github.com/wasmerio/wasmer
阅读有关Wasmer的更多信息
https://medium.com/wasmer/

Run any code on any client.With WebAssembly and Wasmer.
在任何客户端上运行任何代码。用webassembly和waser。

Wasmer 是支持 WASI 和 Emscripten 的 WebAssembly 运行时,提供基于 WebAssembly 的超轻量级容器,其可以在任何地方运行:从桌面端到云端、以及 IoT 设备,并且能嵌入在任何编程语言中。

Wasmer可以是说在WASI生态中响应速度仅次于Mozilla的组织,他们号称打造了
一款可以让代码“一次构建,处处运行”(Build Once, Run Anywhere.)的运行时环境,该环境可以运行ECMAScripten标准与WASI标准的wasm栈机码。并且方便为wasm代码分发,该组织开发了类似于nodejs生态中npm的包管理工具wapm,这样用户就可以很轻松地发布自己的程序,以及利用他人的程序了–这促进了WASM生态的发展,同时作为生态底层的领导者,Wasmer也将拥有更多发言权。

官方表示,不只在浏览器中,WebAssembly(Wasm)会是未来软件执行和容器化的关键组件。透过Wasm进行软件容器化,可以让二进制文件在不需要修改的前提下,於Linux、macOS、Windows以及网页浏览器等环境执行,并保护主机不受恶意程序码、错误和臭虫影响。Wasmer容器的特点之一,便是在Docker容器因过於笨重无法运作之处,执行容器化工作负载。

通过设计,WebAssembly模块运行所在的环境与基础主机系统的本机功能完全隔离(或沙盒化)。这意味着默认情况下,Wasm模块被设计为仅执行纯计算。

因此,通常无法从WASM访问“ OS”级资源,例如文件描述符,网络套接字,系统时钟和随机数。但是,在许多情况下,Wasm模块需要执行的工作不仅仅是执行纯计算。它们必须与本机“ OS”功能交互。

Wasmer旨在提供三个关键功能:

  • 使程序能够以任何编程语言运行
  • 可移植的二进制文件能够在Wasmer支持的任何“ OS”(例如Linux,macOS,Windows和FreeBSD)上运行,且无需修改。
  • 充当Wasm模块通过诸如WASI和Emscripten之类的ABI与本机“ OS”功能交互的安全桥。

Wasmer 是用 Rust 写的。

2. wasmer特性

  • 快速又安全. Wasmer在完全沙盒化的环境中以“接近本机”的速度运行 WebAssembly。
  • 可插拔. Wasmer 可以根据你的需求支持不同的编译框架 (LLVM,Cranelift …).
  • 通用的. 你可以在任何平台(macOS, Linux and Windows) 和芯片组运行 Wasmer.
  • 符合标准. 运行时通过了官方WebAssembly测试集、支持WASIEmscripten.

3. 对比wasmtime

有哪些优秀的 WebAssembly 编译器与运行时
参考URL: https://time.geekbang.org/column/article/292041

对比wasmtime,其生态更加完善。提供了 wapmWebAssembly.sh。 **Wasmer 在 WebAssembly 服务器运行时领域居于领先地位,其主要优势体现在接近原生水平的执行速度。**事实上,单凭速度这一点, Wasmer 就足以技压群雄。

  • wapm 是WebAssembly包管理器。
  • WebAssembly shell程序是一个在线shell程序,您可以在其中拖放WebAssembly模块以进行尝试,还可以执行WAPM中可用的WASI模块的所有命令。

4. Wasmer 快速开始

Wasmer 不需要安装其他依赖. 你可以使用以下安装程序进行安装:

# curl https://get.wasmer.io -sSfL | sh

执行WebAssembly文件
安装Wasmer之后,你已经准备好执行第一个WebAssemby文件了!

注意:亲测,安装脚本有连接的一些网址,需要翻墙!
安装完成后,会默认安装到 /root/.wasmer 目录下

安装完成,通过执行wasmer命令检查一下:
学新通wapm是webassembly包管理器
wapm is the WebAssembly Package Manager

安装wasmer会自动帮助我们安装wapm,我们执行 wapm 命令:
学新通

安装遇到问题
/root/.wasmer/bin/wasmer: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory

安装 libncurses5 就可解决
sudo apt-get install libncurses5

5. wasmer命令使用

root@aaa2:~# wasmer --help
wasmer 2.1.0
Wasmer Engineering Team <engineering@wasmer.io>
WebAssembly standalone runtime.

USAGE:
    wasmer <SUBCOMMAND>

FLAGS:
    -h, --help
            Prints help information

    -V, --version
            Prints version information


SUBCOMMANDS:
    binfmt         Unregister and/or register wasmer as binfmt interpreter
    cache          Wasmer cache
    compile        Compile a WebAssembly binary
    config         Get various configuration information needed to compile programs which use Wasmer
    create-exe     Compile a WebAssembly binary into a native executable
    help           Prints this message or the help of the given subcommand(s)
    inspect        Inspect a WebAssembly file
    run            Run a WebAssembly file. Formats accepted: wasm, wat
    self-update    Update wasmer to the latest version
    validate       Validate a WebAssembly binary
    wast           Run spec testsuite
root@aaa2:~#
学新通

在你的Rust应用程序中使用Wasmer

如何使用 wasmer 和 rust 构建插件系统
参考URL: https://www.cnblogs.com/ishenghuo/p/12105636.html

https://docs.wasmer.io/integrations/rust

Wasmer是一个可以嵌入到Rust应用中的wasm解释器

三、将rust官方demo猜数字编译为WASI目标并使用Wasmer运行

[推荐阅读官方原版]猜数字官方demo:
https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html
Rust实例学习(猜数字游戏)
参考URL: https://blog.csdn.net/Alexhcf/article/details/107770784

📦 Wasmer 运行时能以库的形式嵌入到不同的语言,因此你可以在任何地方使用WebAssembly.

程序运行逻辑:程序将会随机生成一个 1 到 100 之间的随机整数。接着它会请玩家猜一个数并输入,然后提示猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。

1. 设置一个新项目

$ cargo new guessing_game
$ cd guessing_game

cargo new generates a “Hello, world!” program for you. Check out the src/main.rs file:
cargo new 会产生“Hello,World!”。查看src/main.rs文件

1) 获取用户猜测并打印

重新打开src/main.rs文件。您将在此文件中编写所有代码。

猜测游戏程序的第一部分将要求用户输入,输入的进程,并检查输入是否处于预期形式。要开始,我们将允许玩家输入猜测。在src/main.rs中输入:

use std::io;

fn main() {
    println!("Guess the number!");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}

学新通
  • use std::io; 为了获取用户输入并打印结果作为输出,我们需要将 io(输入/输出)库引入当前作用域。io 库来自于标准库(也被称为 std)。 如果程序的开头没有写 use std::io 这一行,也可以把函数调用写成 std::io::stdin。
    io::stdin()
        .read_line(&mut guess)
  • .read_line(&mut guess),调用 read_line 方法从标准输入句柄获取用户输入。我们还向 read_line() 传递了一个参数:&mut guess
    read_line 的职责是,无论用户在标准输入中键入什么内容,都将其存入一个字符串中,因此它需要字符串作为参数。这个字符串参数应该是可变的,以便 read_line 将用户输入附加上去
  • & 表示这个参数是一个 引用(reference),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。它像变量一样,默认是不可变的。因此,需要写成 &mut guess 来使其可变,而不是 &guess。
  • let mut guess = String::new(); 这条语句创建了一个存储用户输入的地方,注意这是一个 let 语句,用来创建 变量(variable),例如
    let foo = bar; //新建了一个叫做 foo 的变量值为 bar

read_line 将用户输入附加到传递给它的字符串中,不过它也返回一个值——在这个例子中是 io::Result。

Result 类型是 枚举(enumerations),通常也写作 enums。枚举类型持有固定集合的值,这些值被称为枚举的 成员(variants)
Result 的成员是 Ok 和 Err,Ok 成员表示操作成功,内部包含成功时产生的值。Err 成员则意味着操作失败,并且包含失败的前因后果。
io::Result 的实例拥有 expect 方法。如果 io::Result实例的值是 Err,expect 会导致程序崩溃,并显示当做参数传递给 expect 的信息。如果 read_line 方法返回 Err,则可能是来源于底层操作系统错误的结果。如果 io::Result 实例的值是 Ok,expect 会获取 Ok 中的值并原样返回。如果不调用 expect,程序也能编译,不过编译过程会出现一个警告。
Rust 编译器警告我们没有使用 read_line 的返回值 Result,说明有一个可能的错误没有处理。
消除警告的正确做法是实际编写错误处理代码,不过由于我们就是希望程序在出现问题时立即崩溃,所以直接使用 expect。

  • 使用 println! 占位符打印值
println!("You guessed: {}", guess);

这行代码打印存储用户输入的字符串。第一个参数是格式化字符串,里面的 {} 是预留在特定位置的占位符。

现在我们知道了 let mut guess 会引入一个叫做 guess 的可变变量。等号(=)的右边是 guess 所绑定的值,它是 String::new 的结果,这个函数会返回一个 String 的新实例。String 是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。

  • new 函数创建了一个新的空字符串,你会发现很多类型上有 new 函数,因为它是创建类型实例的常用函数名。

至此,猜数字游戏的第一部分已经完成:我们从键盘获取输入并打印了出来。
学新通

2) 生成一个随机数字

接下来,需要生成一个数字,好让用户来猜。数字应该每次都不同,这样重复玩才不会乏味;范围应该在 1 到 100 之间,这样才不会太困难。Rust 标准库中尚未包含随机数功能。然而,Rust 团队还是提供了一个 rand crate。

在我们使用 rand 编写代码之前,需要修改 Cargo.toml 文件,引入一个 rand 依赖。现在打开这个文件并在底部的 [dependencies] 片段标题之下添加:
文件名: Cargo.toml

[dependencies]
rand = "0.8.3"

[dependencies] 部分告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。
在这里,我们使用语义化版本 0.8.3 来指定 rand crate。Cargo 理解语义化版本(Semantic Versioning)(有时也称为 SemVer),这是一种定义版本号的标准。0.8.3 事实上是 ^0.5.5 的简写,它表示 “任何与 0.8.3 版本公有 API 相兼容的版本”。

现在重新构建项目

$ cargo build

学新通

现在我们有了一个外部依赖,Cargo 从 registry 上获取所有包的最新版本信息,这是一份来自 Crates.io 的数据拷贝。Crates.io 是 Rust 生态环境中的开发者们向他人贡献 Rust 开源项目的地方。

在更新完 registry 后,Cargo 检查 [dependencies] 部分并下载缺失的 crate 。从上图种可以看到,虽然只声明了 rand 一个依赖,然而 Cargo 还是额外获取了 libc 和 rand_core 的拷贝,因为 rand 依赖 libc 来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。

Cargo.lock 文件

Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 rand crate 的 0.5.6 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?

这个问题的答案是 Cargo.lock 文件。它在第一次运行 cargo build 时创建,并放在 guessing_game 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 Cargo.lock 文件。当将来构建项目时,Cargo 会发现 Cargo.lock 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,多亏有了 Cargo.lock 文件,项目会持续使用 0.8.3 直到你显式升级。

更新 crate 到一个新版本

当你 确实 需要升级 crate 时,Cargo 提供了另一个命令,update,它会忽略 Cargo.lock 文件,并计算出所有符合 Cargo.toml 声明的最新版本。如果成功了,Cargo 会把这些版本写入 Cargo.lock 文件。

不过,Cargo 默认只会寻找大于 0.8.3 而小于 0.9.0 的版本。

3) 生成一个随机数

文件名: src/main.rs

use std::io;
use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("The secret number is: {}", secret_number);

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);
}
学新通

代码解释
第二行新增了一行 use:use rand::Rng。Rng 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。

新增加的代码打印出了秘密数字。这在开发程序时很有用,因为可以测试它,不过在最终版本中会删掉它。如果游戏一开始就打印出结果就没什么可玩的了!

rand::thread_rng 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取随机种子。接下来,调用随机数生成器的 gen_range 方法。这个方法由刚才引入到作用域的 Rng trait 定义。gen_range 方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 1 和 101 来请求一个 1 和 100 之间的数。

注意:例如,假设你对 rand crate 中的其他功能感兴趣,你可以运行 cargo doc --open 并点击左侧导航栏中的 rand

4) 比较数字

现在有了用户输入和一个随机数,我们可以比较它们

Filename: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    // --snip--
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("The secret number is: {}", secret_number);

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
学新通

新代码的第一行是另一个 use,从标准库引入了一个叫做 std::cmp::Ordering 的类型。同 Result 一样, Ordering 也是一个枚举,不过它的成员是 Less、Greater 和 Equal。这是比较两个值时可能出现的三种结果。

接着,底部的五行新代码使用了 Ordering 类型,cmp 方法用来比较两个值并可以在任何可比较的值上调用。它获取一个被比较值的引用:这里是把 guess 与 secret_number 做比较。 然后它会返回一个刚才通过 use 引入作用域的 Ordering 枚举的成员。使用一个 match 表达式,根据对 guesssecret_number 调用 cmp 返回的 Ordering 成员来决定接下来做什么。

一个 match 表达式由 分支(arms) 构成。一个分支包含一个 模式(pattern)和表达式开头的值与分支模式相匹配时应该执行的代码。Rust 获取提供给 match 的值并挨个检查每个分支的模式。match 结构和模式是 Rust 中强大的功能,它体现了代码可能遇到的多种情形,并帮助你确保没有遗漏处理。

假设用户猜了 50,这时随机生成的数字是 38。比较 50 与 38 时,因为 50 比 38 要大,cmp 方法会返回 Ordering::Greater。Ordering::Greater 是 match 表达式得到的值。它检查第一个分支的模式,Ordering::Less 与 Ordering::Greater并不匹配,所以它忽略了这个分支的代码并来到下一个分支。下一个分支的模式是 Ordering::Greater,正确 匹配!这个分支关联的代码被执行,在屏幕打印出 Too big!。match 表达式就此终止,因为该场景下没有检查最后一个分支的必要。

错误的核心表明这里有 不匹配的类型(mismatched types)。因为Rust 有一个静态强类型系统,所以同时也有类型推断。当我们写出 let guess = String::new() 时,Rust 推断出 guess 应该是 String 类型,并不需要我们写出类型。另一方面,secret_number,是数字类型。 1 到 100 之间的值可能包含以下的几个数字类型:32 位数字 i32;32 位无符号数字 u32;64 位数字 i64 等等。Rust 默认使用 i32,所以它是 secret_number 的类型,除非增加类型信息,或任何能让 Rust 推断出不同数值类型的信息。这里错误的原因在于 Rust 不会比较字符串类型和数字类型。

所以我们必须把从输入中读取到的 String 转换为一个真正的数字类型,才好与随机数字进行比较。这可以通过在 main 函数体中增加如下两行代码来实现:

// --snip--

    let mut guess = String::new();

    io::stdin().read_line(&mut guess)
        .expect("Failed to read line");

	let guess: u32 = guess.trim().parse().expect("Please type a number!");//新代码

    println!("You guessed: {}", guess);

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
学新通
let guess: u32 = guess.trim().parse().expect("Please type a number!");

这里创建了一个叫做 guess 的变量。不过等等,不是已经有了一个叫做 guess 的变量了吗?确实如此,不过 Rust 允许用一个新值来 隐藏 (shadow) guess 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 guess 变量的名字,而不是被迫创建两个不同变量,诸如 guess_str 和 guess 之类。

这里将 guess 绑定到 guess.trim().parse() 表达式上。表达式中的 guess 是包含输入的原始 String 类型。String 实例的 trim 方法会去除字符串开头和结尾的空白字符。u32 只能由数字字符转换,不过用户必须输入 Enter 键才能让 read_line 返回,然而用户按下 enter 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 5 并按下 enter,guess 看起来像这样:5\n。\n 代表 “换行”,回车键。trim 方法消除 \n,只留下 5。

字符串的 parse 方法 将字符串解析成数字。因为这个方法可以解析多种数字类型,因此需要告诉 Rust 具体的数字类型,这里通过 let guess: u32 指定。guess 后面的冒号(:)告诉 Rust 我们指定了变量的类型。

parse 调用很容易产生错误。例如,字符串中包含 A👍%,就无法将其转换为一个数字。因此,parse 方法返回一个 Result 类型。像之前 “使用 Result 类型来处理潜在的错误” 讨论的 read_line 方法那样,再次按部就班的用 expect 方法处理即可。如果 parse 不能从字符串生成一个数字,返回一个 Result 的 Err 成员时,expect 会使游戏崩溃并打印附带的信息。如果 parse 成功地将字符串转换为一个数字,它会返回 Result 的 Ok 成员,然后 expect 会返回 Ok 值中的数字。

5) 使用循环来实现多次猜测

使用loop 关键字创建一个无限循环。将其加入后,用户可以反复猜测
文件名: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    // --snip--

    println!("The secret number is: {}", secret_number);

    loop {
        println!("Please input your guess.");

        // --snip--


        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = guess.trim().parse().expect("Please type a number!");

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => println!("You win!"),
        }
    }
}
学新通
6) 猜测正确后退出

增加一个 break 语句,在用户猜对时退出游戏
文件名: src/main.rs

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("The secret number is: {}", secret_number);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = guess.trim().parse().expect("Please type a number!");

        println!("You guessed: {}", guess);

        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
学新通

退出循环也意味着退出程序,因为循环是 main 的最后一部分。

7) 处理无效输入

为了进一步改善游戏性,不要在用户输入非数字时崩溃,需要忽略非数字,让用户可以继续猜测。可以通过修改 guess 将 String 转化为 u32 那部分代码来实现

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    println!("The secret number is: {}", secret_number);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        // --snip--

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        // --snip--

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
学新通

将 expect 调用换成 match 语句,是从遇到错误就崩溃转换到真正处理错误的惯用方法。须知 parse 返回一个 Result 类型,而 Result 是一个拥有 Ok 或 Err 成员的枚举。这里使用的 match 表达式,和之前处理 cmp 方法返回 Ordering 时用的一样。

如果 parse 能够成功的将字符串转换为一个数字,它会返回一个包含结果数字的 Ok。这个 Ok 值与 match 第一个分支的模式相匹配,该分支对应的动作返回 Ok 值中的数字 num,最后如愿变成新创建的 guess 变量。

如果 parse 不 能将字符串转换为一个数字,它会返回一个包含更多错误信息的 Err。Err 值不能匹配第一个 match 分支的 Ok(num) 模式,但是会匹配第二个分支的 Err() 模式: 是一个通配符值,本例中用来匹配所有 Err 值,不管其中有何种信息。所以程序会执行第二个分支的动作,continue 意味着进入 loop 的下一次循环,请求另一个猜测。这样程序就有效的忽略了 parse 可能遇到的所有错误!
最后删掉打印秘密数字的 println!

2. 最终完整代码

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..101);

    loop {
        println!("Please input your guess.");

        let mut guess = String::new();

        io::stdin()
            .read_line(&mut guess)
            .expect("Failed to read line");

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => continue,
        };

        println!("You guessed: {}", guess);

        match guess.cmp(&secret_number) {
            Ordering::Less => println!("Too small!"),
            Ordering::Greater => println!("Too big!"),
            Ordering::Equal => {
                println!("You win!");
                break;
            }
        }
    }
}
学新通

此时,您已成功构建了猜测游戏。祝贺!

这个项目是向您介绍许多新的 Rust 概念:letmatch,函数,使用crates等等。

3. 是时候编译成WASI目标了

读到这里,读者可以发现前文根本没涉及到WASI,甚至没有涉及WASM。这因为WASI作为应用与运行时交互的接口,被rust编译器封装成为编译目标,读者只需要编译到对应目标即可让自己的程序在对应平台上运行. 这是Rust编程语言现代化与工程学的体现:

一般应用研发工程师可以通过使用已经适配所需平台的底层库(这些底层库通常已经针对所有支持平台做了最优化适配),就能让自己的应用支持对应的平台而无需重新编写针对某平台的特化版本源码

安装 target
看一下当前安装的 Rust 版本

# rustc -V
rustc 1.57.0 (f1edd0429 2021-11-29)

执行

rustup target list

可以看到如下结果(列表很长,已把wasm相关的筛选出来),这几个是 Rust 支持的 wasm 相关的编译目标。

root@aaa2:~# rustup target list  |grep asm
asmjs-unknown-emscripten
wasm32-unknown-emscripten
wasm32-unknown-unknown
wasm32-wasi
root@aaa2:~#

前 3 个,可以理解成编译出来的文件,用于加载到浏览器 web 页面(或有类似功能的运行时中)运行,它没有IO功能,因此常用于一些纯计算型任务(比如,把js中的计算密集型工作放wasm中来做)。具体可参考 https://rustwasm.github.io/book/introduction.html

这里我们主要使用 wasi。
WASI 是 The WebAssembly System Interface 的缩写,简单来说就是一套接口标准,将 wasm 的应用领域从 web 中拓展到更广阔的各个平台中去(可以想像成类似 libc 之类的东西,但是是跨平台的)。想了解更多: https://wasi.dev

使用前,需要添加 wasi target 基础包

root@aaa2:~# rustup target add wasm32-wasi
info: downloading component 'rust-std' for 'wasm32-wasi'
info: installing component 'rust-std' for 'wasm32-wasi'
 16.2 MiB /  16.2 MiB (100 %)   2.9 MiB/s in  5s ETA:  0s
root@aaa2:~#

会自动下载 rust-std 的 wasm32-wasi 目标版本。添加好后,执行

cargo build --target=wasm32-wasi

将工程编译到 wasi 目标。

执行完成后,在target目录下,就可以看到对应的 .wasm结尾文件。

  1. 运行 .wasm 文件

四、wasmtime

Mozilla下的一个组织CraneStation所开发的WASM运行时wasmtime, 该运行时对WASI支持比较高.

未深入研究!暂时记录在这里。

参考

高性能WebAssembly Runtime开源专案Wasmer 1.0登场
参考URL: https://baijiahao.百度.com/s?id=1688290060748803927
【译】用 Wasmer 进行插件开发 1
https://www.cnblogs.com/ishenghuo/p/12105636.html
[推荐]WASI探索(二) – 将猜数字编译为WASI目标并使用Wasmer运行
参考URL: https://blog.csdn.net/shangsongwww/article/details/120269648

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgaeeib
系列文章
更多 icon
同类精品
更多 icon
继续加载