Open hunter-ji opened 1 year ago
催更
催更
多谢,最近痴迷于写产品😅...我会尽快肝起来的
还么更,再催一下(⊙o⊙)…
还么更,再催一下(⊙o⊙)…
抱歉,最近工作好忙😭,本来写了一部分了,一直拖着,今天终于写完了:使用Rust和WebAssembly整花活儿(四)——更小更小的wasm文件体积.md
意犹未尽呀,还会更吗?好想知道对于rust->wasm,作者是咋学习的
后续还会更的,不过这系列写起来真的很耗时间。 学习过程其实挺简单的,就是一开始我使用go写wasm的,但是发现体积太大,直接就上rust了,根本性解决了。
这个系列还会更新吗想看呢
这个系列还会更新吗想看呢
会的,我会努力的,现在在进行主线任务😮💨
一. 前言
在上一篇文章《使用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-wasm
二. 环境
三. 函数的相互调用
1. JS调用Rust函数
其实,在本系列文章的第一篇中,就是使用的JS调用Rust函数作为案例来演示的,这里依然以此为例,主要讲一下要点。
首先,声明一个Rust函数:
此处需要注意的要点如下:
wasm_bindgen
pub
声明#[wasm_bindgen]
宏来将Rust函数导出为WebAssembly模块的函数接着,编译成wasm文件:
然后,在JS中调用该函数:
最后,启动http server,在浏览器的控制台中可以看到
the result from rust is: 3
,表明调用成功!2. Rust调用JS函数
2.1 指定JS对象
在Rust中调用JS函数,需要进行指定JS对象,也就是说,得明确告诉Rust,这个JS函数是从JS哪儿拿来的用的。
主要在于下面两个方式:
js_namespace
,则所有的导出函数将被放置在全局命名空间下。js_name
,则导出函数的名称将与Rust中的函数名称相同。2.2 JS原生函数
对于一些JS原生函数,在Rust中,需要去寻找替代方案,比如我们上一篇文章中讲的
console.log()
函数,是不是觉得好麻烦啊!那么,你想直接在Rust中调用JS原生函数吗?!
此处,就以
console.log()
函数为例,直接在Rust中引入并调用,免去替代方案的烦恼。首先,给出Rust代码:
如上代码中,
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中调用下:
可以在浏览器的控制台中看到
hello, javascript!
。妙啊!其实对于
console.log()
而言,还有另一种调用方式,那就是使用js_namespace
和js_name
同时指定。或许,你会问,这有什么不同吗?是的,这有些不同。
不知道你是否发现,当前这个案例中,指定了
js_namespace
为console
,但是真实执行的函数是log()
,那么这个log
函数的指定,其实是体现在Rust中同样的函数名log
。也就是说,该案例的log()
就是console.log()
中的log()
。我们来换个名字看看,将原来的
log()
换成log2()
:然后编译后去控制台看看,就会看到报错:
因此,当我们使用
js_namespace
和js_name
结合的方式,在此处是可以进行自定义函数名的。看一下Rust代码:
此处,重新定义了一个函数
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
,写入一个函数:当前的文件结构关系如下:
其中,
index.js
和lib.rs
,以及hello_wasm_bg.wasm
都是不在同一级别的,index.js
都在其它两个文件的上一级。记住这个结构关系!然后,在
lib.rs
中,指定函数:其中,
raw_module = "../index.js"
的意思是,指定对应的index.js
文件,大家应该清楚,此处指定的是刚刚创建的index.js
。raw_module
的作用就是用来指定js文件的。这段代码在前端,可以等同于:
这样在前端都不用引入了,直接在Rust中引入了,感觉还有点奇妙的。
接着,在Rust调用该函数:
最后,在前端调用,编译后,在浏览器的控制台中可以看到输出结果了!
总结一下,这里有几个注意点:
raw_module
只能用来指定相对路径,并且,大家可以在浏览器的控制台中注意到,此处的../
的相对路径,其实是以wasm文件而言的相对路径,这里一定要注意呀!四. JS调用Rust的struct
现在,来点炸裂的,JS调用Rust的struct?!
JS中连struct都没有,这玩意儿导出来会是什么样,得怎么在JS中调用呢?!
首先,定义一个struct,并且声明几个方法:
此处,声明了一个struct名为
User
,包含name
和age
两个字段,并声明了new
、print_user
和set_age
方法。其中还有一个未见过的
#[wasm_bindgen(constructor)]
,constructor
用于指示被绑定的函数实际上应该转换为调用 JavaScript 中的 new 运算符。或许你还不太清晰,继续看下去,你就会明白了。接着,在JS中调用这个struct,和其方法:
可以看到,这里的用法就很熟悉了!
大概想一下,在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的交互而言,还是非常棒的体验。