我正在参与「启航方案」

一. 前语

在上一篇文章《运用Rust和WebAssembly整花活儿(二)——DOM和类型转化》中,描述了运用Rust操作DOM,并完成Rust与JS类型转化的多种办法。

在开发 Web 应用程序时,运用 Rust 编写的 Wasm 模块能够供给更高的性能和更好的安全性。可是,为了与现有的 JavaScript 代码集成,有必要完成 Rust 与 JS 之间的交互。Rust 与 JS 交互的首要意图是将两种言语的优势结合起来,以完成更好的 Web 应用程序。

根据上一篇文章中,Rust与JS的类型转化的多种办法,本篇文章继续深化Rust与JS的交互。

首要,Rust与JS的类型转化,能够完成变量的传递,那么变量是要用在哪里呢?那必定是函数了!

所以,本篇文章来叙述一下Rust与JS的函数彼此调用,根据此,能够完成大量日常功用开发。

而且,还将会叙述一下,怎么导出Rust的struct给JS调用。

是的,没错,在JS调用Rust的struct!一开始看到这个功用的时候,我的脑子是有点迸裂的……

本篇文章中,将根据上一篇文章中创立的项目来继续开发。

源码:github.com/Kuari/hello…

二. 函数的彼此调用

1. JS调用Rust函数

其实,在本系列文章的榜首篇中,便是运用的JS调用Rust函数作为事例来演示的,这里仍然以此为例,首要讲一下关键。

首要,声明一个Rust函数:

use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

此处需求注意的关键如下:

  • 引入wasm_bindgen
  • 声明一个函数,运用pub声明
  • 在函数上运用#[wasm_bindgen]宏来将Rust函数导出为WebAssembly模块的函数

接着,编译成wasm文件:

wasm-pack build --target web

然后,在JS中调用该函数:

<script type="module">
    import init, { add } from './pkg/hello_wasm.js';
    const run = async () => {
        await init();
        const result = add(1, 2);
        console.log(`the result from rust is: ${result}`);
    }
    run();
</script>

最终,启动http server,在浏览器的操控台中能够看到the result from rust is: 3,表明调用成功!

2. Rust调用JS函数

2.1 指定JS目标

在Rust中调用JS函数,需求进行指定JS目标,也便是说,得明晰告知Rust,这个JS函数是从JS哪儿拿来的用的。

首要在于下面两个办法:

  • js_namespace: 是一个可选的特点,用于指定一个JavaScript命名空间,其间包括将要在wasm模块中导出的函数。假如没有指定js_namespace,则一切的导出函数将被放置在大局命名空间下。
  • js_name: 是另一个可选特点,它用于指定JavaScript中的函数称号。假如没有指定js_name,则导出函数的称号将与Rust中的函数称号相同。

2.2 JS原生函数

关于一些JS原生函数,在Rust中,需求去寻找代替方案,比方咱们上一篇文章中讲的console.log()函数,是不是觉得好麻烦啊!

那么,你想直接在Rust中调用JS原生函数吗?!

此处,就以console.log()函数为例,直接在Rust中引入并调用,免除代替方案的烦恼。

首要,给出Rust代码:

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(message: &str);
}
#[wasm_bindgen]
pub fn call_js_func() {
    log("hello, javascript!");
}

如上代码中,call_js_func函数,顾名思义,此处是调用了js函数,并传入参数hello, javascript!

那么,call_js_func函数上方的代码,咱们来一步步解析一下:

  1. 榜首行代码#[wasm_bindgen]是Rust的特点,它告知编译器将函数导出为WebAssembly模块
  2. extern "C"是C言语调用约好,它告知Rust编译器将函数导出为C言语函数
  3. #[wasm_bindgen(js_namespace = console)]告知编译器将函数绑定到JavaScript中的console目标
  4. fn log(message: &str)是一个Rust函数,它承受一个字符串参数,并将其打印到JavaScript中的console目标中

此处与JS交互的关键是js_namespace。在Rust中,js_namespace是用于指定JavaScript命名空间的特点。在WebAssembly中,咱们能够经过它将函数绑定到JavaScript中的目标上。

在上述代码中,#[wasm_bindgen(js_namespace = console)]告知编译器将函数绑定到JavaScript中的console目标。这意味着在JS中运用console.log()函数来调用Rust中的log()函数。

因而,类似的原生函数,都能够运用该办法来完成调用。

最终,咱们在JS中调用下:

<script type="module">
    import init, { call_js_func } from './pkg/hello_wasm.js';
    const run = async () => {
        await init();
        call_js_func();
    }
    run();
</script>

能够在浏览器的操控台中看到hello, javascript!。妙啊!

其实关于console.log()而言,还有另一种调用办法,那便是运用js_namespacejs_name同时指定。

或许,你会问,这有什么不同吗?是的,这有些不同。

不知道你是否发现,当前这个事例中,指定了js_namespaceconsole,可是实在执行的函数是log(),那么这个log函数的指定,其实是体现在Rust中同样的函数名log。也便是说,该事例的log()便是console.log()中的log()

咱们来换个姓名看看,将原来的log()换成log2()

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log2(message: &str);
}
#[wasm_bindgen]
pub fn call_js_func() {
    log2("hello, javascript!")
}

然后编译后去操控台看看,就会看到报错:

TypeError: console.log2 is not a function

因而,当咱们运用js_namespacejs_name结合的办法,在此处是能够进行自界说函数名的。

看一下Rust代码:

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(message: &str);
    #[wasm_bindgen(js_namespace = console, js_name = log)]
    fn log_str(message: &str);
}
#[wasm_bindgen]
pub fn call_js_func() {
    log_str("hello, javascript!")
}

此处,从头界说了一个函数log_str,可是其指定了js_namespace = consolejs_name = log,那么此处,就能够运用自界说的函数名。

直接编译后,在操控台看一下,能够直接看到正常输出:hello, javascript!

总结一下,假如没有指定js_name,则 Rust 函数称号将用作 JS 函数称号。

2.3 自界说JS函数

在必定场景下,需求运用Rust调用JS函数,比方关于一些关于JS而言更有优势的场景——用JS操作DOM,用Rust核算。

首要,创立一个文件index.js,写入一个函数:

export function addIt(m, n) {
    return m + n;
};

当前的文件结构关系如下:

.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── index.html
├── index.js
├── pkg
│ ├── README.md
│ ├── hello_wasm.d.ts
│ ├── hello_wasm.js
│ ├── hello_wasm_bg.wasm
│ ├── hello_wasm_bg.wasm.d.ts
│ └── package.json
├── src
│ └── lib.rs
└── target
    ├── CACHEDIR.TAG
    ├── debug
    ├── release
    └── wasm32-unknown-unknown

其间,index.jslib.rs,以及hello_wasm_bg.wasm都是不在同一级别的,index.js都在其它两个文件的上一级。记住这个机构关系!

然后,在lib.rs中,指定函数:

#[wasm_bindgen(raw_module = "../index.js")]
extern "C" {
    fn addIt(m: i32, n: i32) -> i32;
}

其间,raw_module = "../index.js"的意思是,指定对应的index.js文件,我们应该清楚,此处指定的是刚刚创立的index.jsraw_module的效果便是用来指定js文件的。

这段代码在前端,能够等同于:

import { addIt } from '../index.js'

这样在前端都不用引入了,直接在Rust中引入了,感觉还有点美妙的。

接着,在Rust调用该函数:

#[wasm_bindgen]
pub fn call_js_func() -> i32 {
    addIt(1, 2)
}

最终,在前端调用,编译后,在浏览器的操控台中能够看到输出成果了!

总结一下,这里有几个注意点:

  1. JS的函数有必要要export,否则将无法调用;
  2. raw_module只能用来指定相对路径,而且,我们能够在浏览器的操控台中注意到,此处的../的相对路径,其实是以wasm文件而言的相对路径,这里必定要注意呀!

三. JS调用Rust的struct

现在,来点迸裂的,JS调用Rust的struct?!

JS中连struct都没有,这玩意儿导出来会是什么样,得怎样在JS中调用呢?!

首要,界说一个struct,而且声明几个办法:

#[wasm_bindgen]
pub struct User {
    name: String,
    age: u32
}
#[wasm_bindgen]
impl User {
    #[wasm_bindgen(constructor)]
    pub fn new(name: String, age: u32) -> User {
        User { name, age }
    }
    pub fn print_user(&self) {
        log(format!("name is : {}, age is : {}", self.name, self.age).as_str());
    }
    pub fn set_age(&mut self, age: u32) {
        self.age = age;
    }
}

此处,声明晰一个struct名为User,包括nameage两个字段,并声明晰newprint_userset_age办法。

其间还有一个未见过的#[wasm_bindgen(constructor)]constructor用于指示被绑定的函数实际上应该转化为调用 JavaScript 中的 new 运算符。或许你还不太明晰,继续看下去,你就会明白了。

接着,在JS中调用这个struct,和其办法:

<script type="module">
    function addIt2(m, n) {
        return m + n;
    };
    import init, { User } from './pkg/hello_wasm.js';
    const run = async () => {
        await init();
        const user = new User('kuari', 20);
        user.set_age(21);
        user.print_user();
    }
    run();
</script>

能够看到,这里的用法就很了解了!

大概想一下,在Rust中要怎么调用?也便是直接new一个——User::new('kuari', 20)

此处在JS中,也是如此,先new一个!

然后很自然地调用struct的办法。

编译后,打开浏览器,能够在操控台看到输出:name is : kuari, age is : 21

其实,或许我们会很好奇,起码我对错常好奇的,Rust的struct在JS中到底是一个怎样的存在呢?

这里直接增加一个console.log(user),就能够在输出看到。那么到底在JS中是一个怎样的存在呢?请各位着手打印一下看看吧!:P

四. 总结

本篇文章中,首要叙述了Rust与JS的交互,体现在Rust与JS的彼此调用,这是建立在上一篇文章中类型转化的基础上的。

Rust与JS的函数彼此调用的学习本钱还是较大的,而且对比Go写wasm,Rust的颗粒度对错常细的,几乎能够说是为所欲为了。

比较迸裂的便是Rust的struct导出给JS用,这关于Rust与JS的交互而言,还对错常棒的体会。