我正在参与「启航方案」
一. 前语
在上一篇文章《运用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
函数上方的代码,咱们来一步步解析一下:
- 榜首行代码
#[wasm_bindgen]
是Rust的特点,它告知编译器将函数导出为WebAssembly模块 -
extern "C"
是C言语调用约好,它告知Rust编译器将函数导出为C言语函数 -
#[wasm_bindgen(js_namespace = console)]
告知编译器将函数绑定到JavaScript中的console目标 -
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_namespace
和js_name
同时指定。
或许,你会问,这有什么不同吗?是的,这有些不同。
不知道你是否发现,当前这个事例中,指定了js_namespace
为console
,可是实在执行的函数是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_namespace
和js_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 = console
和js_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.js
和lib.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.js
。raw_module
的效果便是用来指定js文件的。
这段代码在前端,能够等同于:
import { addIt } from '../index.js'
这样在前端都不用引入了,直接在Rust中引入了,感觉还有点美妙的。
接着,在Rust调用该函数:
#[wasm_bindgen]
pub fn call_js_func() -> i32 {
addIt(1, 2)
}
最终,在前端调用,编译后,在浏览器的操控台中能够看到输出成果了!
总结一下,这里有几个注意点:
- JS的函数有必要要export,否则将无法调用;
-
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
,包括name
和age
两个字段,并声明晰new
、print_user
和set_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的交互而言,还对错常棒的体会。