暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

写给 Web 开发者的 Rust 语言入门教程(24.WASM)

最后的叶子的奇妙小屋 2021-09-24
2668

之前的文章:

0.引言

1.模块基础

2.变量

3.字符串

4.分支循环

5.数据表示

6.模块化

7.接口抽象

8.泛型

9.错误处理

10.内存布局

11.所有权

12.引用传递

13.引用字段

14.引用计数

15.泛化特化

16.闭包

17.普通宏

18.多线程

19.异步编程

20.工作区

21.测试

22.文档

23.条件编译

本章主要介绍 rust 在浏览器中以 WebAssembly 形式运行的基本方法和编程技巧。

编译到 WebAssembly

rust 支持以 WebAssembly 作为编译目标,从而在浏览器中运行。这样,可以使用 rust 来编写一些逻辑复杂的模块,代替性能和底层编程体验不佳的 JavaScript 。

想要在浏览器中运行 rust 代码,首先需要安装 wasm-pack 工具:

https://rustwasm.github.io/wasm-pack/

然后在 Cargo.toml 中加入 cdylib 编译目标和几个常用的依赖项:

    [lib]
    crate-type = ["rlib", "cdylib"] # 必须包含 cdylib


    [dependencies]
    wasm-bindgen = "0.2" # WebAssembly 辅助工具
    js-sys = "0.3" # JavaScript 接口定义
    console_error_panic_hook = "0.1" # 错误输出辅助工具
    log = "0.4"
    console_log = "0.2" # 将日志输出到 console 的工具


    # Web 接口定义
    [dependencies.web-sys]
    version = "0.3"
    features = [ # 必须罗列出需要用到的 Web 对象
    "Window",
    "Document",
    "HtmlElement",
    "Element",
    ]
    复制

    这个 crate 本质上是一个 lib crate ,所以入口文件是 src/lib.rs ,在其中可以有一个起始函数,例如:

      #[macro_use] extern crate log;
      use wasm_bindgen::prelude::*;


      // 这个函数会在初始化时执行
      #[wasm_bindgen(start)]
      pub fn wasm_main() {
      // 初始化错误输出模块
      console_error_panic_hook::set_once();
      // 初始化日志模块
      console_log::init_with_level(log::Level::Trace).unwrap();
      // 输出一句日志(显示在浏览器 console 中)
      debug!("Initialized!");
      }
      复制

      编译这个模块时,通常可以使用 wasm-pack build 命令,不过这样编译出来的模块还需要使用 webpack 等 web 打包工具来进一步处理。如果只需要简单测试的话,可以使用这个命令来快速生成可直接在 HTML 中调用的文件:

        wasm-pack build --target no-modules
        复制

        它会在代码 pkg 子目录下生成编译结果。

        加载 WebAssembly 模块

        生成的编译结果,最主要的是一个 js 文件和一个 wasm 文件。它们需要使用一个 HTML 文件来引导启动(其中需要正确指定这两个文件的路径),例如 index.html

          <!DOCTYPE html>
          <html>
          <head>
          <meta charset="UTF-8">
          </head>
          <body></body>


              <!-- my_wasm 是 crate 名称 -->
          <script src="pkg/my_wasm.js"></script>
              <script>
               wasm_bindgen('pkg/my_wasm_bg.wasm')
              </script>
          </html>
          复制

          然后,启动一个 HTTP 服务器使这些文件能被正确加载。如果本地没有易用的 HTTP 服务器,可以安装 basic-http-server 工具:

            cargo install basic-http-server
            复制

            然后在 HTML 文件所在的目录中启动它:

              basic-http-server -x
              复制

              在浏览器中打开 http://127.0.0.1:4000/ 就可以载入这个 HTML 文件了。

              这个 HTML 载入时,会同时加载上面编译好的 rust 模块并执行初始化函数、在浏览器的 F12 console 中输出日志。

              使用 web 接口

              需要注意的是,部分标准库接口不能在浏览器环境中使用,如 println! 和多线程。但 web 接口是可用的。

              标准的 web 接口可以使用 web_sys 来引用,例如:

                #[macro_use] extern crate log;
                use wasm_bindgen::prelude::*;


                #[wasm_bindgen(start)]
                pub fn wasm_main() {
                console_error_panic_hook::set_once();
                console_log::init_with_level(log::Level::Trace).unwrap();
                debug!("Initialized!");


                // 获取浏览器 window 对象
                let window = web_sys::window().unwrap();
                // 获取浏览器 document 对象
                let document = window.document().unwrap();
                // 获取 document.body
                let body = document.body().unwrap();
                // 写 body.innerHTML
                body.set_inner_html("Hello world!");
                }
                复制

                web_sys 中的各个接口名称与对应的 web 接口相仿,不过有更为明确的类型限制。具体接口定义可检索它的文档。

                https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html

                如果调用的是 JavaScript 的内置对象,如 Date 对象、 Math 对象,则需要使用 js_sys ,例如:

                  #[macro_use] extern crate log;
                  use wasm_bindgen::prelude::*;


                  #[wasm_bindgen(start)]
                  pub fn wasm_main() {
                  console_error_panic_hook::set_once();
                      console_log::init_with_level(log::Level::Trace).unwrap();


                  // 获取当前时间戳
                  let timestamp = js_sys::Date::now();
                  debug!("{}", timestamp);
                  }
                  复制

                  导入自定义接口

                  如果想要使用 JavaScript 编写的接口,可以在 rust 中添加接口定义后使用。例如编写了一个 JavaScript aPlusB 函数:

                    <script src="pkg/my_wasm.js"></script>
                    <script>
                    function aPlusB(a, b) {
                    return a + b
                    }
                    wasm_bindgen('pkg/my_wasm_bg.wasm')
                    </script>
                    复制

                    可以在 rust 中声明这个函数,然后调用它:

                      #[macro_use] extern crate log;
                      use wasm_bindgen::prelude::*;


                      #[wasm_bindgen]
                      extern {
                      // js 函数定义
                      #[wasm_bindgen(js_name = "aPlusB")]
                      fn a_plus_b(a: i32, b: i32) -> i32;
                      }


                      #[wasm_bindgen(start)]
                      pub fn wasm_main() {
                      console_error_panic_hook::set_once();
                      console_log::init_with_level(log::Level::Trace).unwrap();


                      // 调用 js 函数
                      let sum = a_plus_b(2, 3);
                      debug!("{}", sum);
                      }


                      复制

                      导出接口

                      rust 也可以提供接口给 JavaScript 调用。例如:

                        #[macro_use] extern crate log;
                        use wasm_bindgen::prelude::*;


                        // 导出一个 struct
                        #[wasm_bindgen]
                        struct Adder {
                        sum: i32,
                        }


                        #[wasm_bindgen]
                        impl Adder {
                        // 构造器
                        #[wasm_bindgen(constructor)]
                        pub fn new() -> Self {
                        Self {
                        sum: 0,
                        }
                        }


                        // 导出一个函数
                        #[wasm_bindgen]
                        pub fn add(&mut self, n: i32) -> i32 {
                        self.sum += n;
                        self.sum
                        }
                        }


                        #[wasm_bindgen(start)]
                        pub fn wasm_main() {
                        console_error_panic_hook::set_once();
                        console_log::init_with_level(log::Level::Trace).unwrap();
                        }
                        复制

                        上面定义的接口就可以在 JavaScript 中调用:

                          <script src="pkg/my_wasm.js"></script>
                          <script>
                          wasm_bindgen('pkg/my_wasm_bg.wasm').then(() => {
                          // 加载完成后可以调用 rust 导出的接口
                          const adder = new wasm_bindgen.Adder()
                          adder.add(2)
                          console.log(adder.add(3)) // 输出 5
                          })
                          </script>
                          复制

                          rust 可以实现浏览器和服务器端的代码复用。不过,浏览器环境下编程依然是一个不同的编程领域,有很多不一样的问题和挑战。关于 rust 的浏览器环境编程相关内容,后面的文章将有更多细节介绍。

                          文章转载自最后的叶子的奇妙小屋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                          评论