作者:京东物流 梁瑞乐
偶然一次机会,接触了Rust的代码。当时想给团队小伙伴做演示,发现自己并不能在移动端按照文档生成演示demo。我就想,要是Rust代码能转化成JavaScript就好了。结果一搜,还真有。
下面整理成文档,分享给大家。为大家解决问题,多提供一种思路、方式、方法。
一、分享的目的:
▪由 Rust、WebAssembly、JavaScript、HTML 和 CSS 开发多语言程序的工作流程。
▪如何设计 API 以最大限度地利用 Rust 和 WebAssembly 的优势以及 JavaScript 的优势。
▪如何调试从 Rust 编译的 WebAssembly 模块。
二、什么是WebAssembly?
WebAssembly (wasm) 是一种具有广泛规范的简单机器模型和可执行格式。它被设计为可移植、紧凑并以本机速度或接近本机速度执行。
作为一种编程语言,WebAssembly 由两种表示相同结构的格式组成,尽管方式不同:
1.该.wat文本格式(称为wat“WebAssemblyText”)使用S 表达式,与 Scheme 和 Clojure等Lisp 语言家族有相似之处。https://en.wikipedia.org/wiki/S-expression
2.二进制格式.wasm是较低级别的,旨在供 wasm 虚拟机直接使用。它在概念上类似于 ELF 和 Mach-O。
有工具,可以从.wat文本格式到.wasm二进制格式的转换。
三、环境准备:
需要标准 Rust 工具链,包括rustup、rustc和cargo。
安装参考:https://www.rust-lang.org/tools/install
四、学习网站:
Rust 和 WebAssembly
wasm-bindgen官网地址
五、练习演示:
下面这段代码项目是用 Rust + JavaScript 编写的,用于 WebAssembly (Wasm) 项目,它与 Web Workers 和 Web 页面交互。代码的主要功能是判断用户输入的数字是否为偶数,并将结果显示在网页上。
1、安装wasm-pack: wasm-pack是一个帮助你构建和打包Rust代码到WebAssembly的工具。
cargo install wasm-pack
2、创建一个新的Rust库项目:
cargo new --lib my_demo cd my_demo
此时,生成文件目录:
3、配置Cargo.toml: 在Cargo.toml文件中添加wasm-bindgen和web-sys依赖项。
[package] authors = ["The wasm-demo Developers"] edition = "2024" name = "wasm-in-web-worker" publish = false version = "0.0.0" [lib] crate-type = ["cdylib"] [dependencies] console_error_panic_hook = { version = "0.1.6", optional = true } wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ['console', 'Document', 'HtmlElement', 'HtmlInputElement', 'MessageEvent', 'Window', 'Worker'] }
在features中,你可以根据需要启用web-sys的特定Web API特性。更多配置,参考学习文档。
4、编写Rust代码: 在src/lib.rs中使用web-sys。
// 代码首先导入了一些 Rust 标准库和 wasm_bindgen 相关的模块,这些模块用于在 Rust 和 JavaScript 之间建立桥梁,以及操作 Web API。 use std::cell::RefCell; use std::rc::Rc; use wasm_bindgen::prelude::*; use web_sys::{console, HtmlElement, HtmlInputElement, MessageEvent, Worker}; // 定义 NumberEval 结构体 // NumberEval 结构体用于存储一个整数,并提供方法来判断该整数是否为偶数。 // new 方法创建 NumberEval 的新实例,初始数字为 0。 // is_even 方法接受一个整数参数,将其存储在结构体中,并返回该数字是否为偶数。 // get_last_number 方法返回结构体中存储的最后一个数字 #[wasm_bindgen] pub struct NumberEval { number: i32, } #[wasm_bindgen] impl NumberEval { // Create new instance. pub fn new() -> NumberEval { NumberEval { number: 0 } } pub fn is_even(&mut self, number: i32) -> bool { self.number = number; self.number % 2 == 0 } pub fn get_last_number(&self) -> i32 { self.number } } // startup 函数是在 Wasm 模块加载时调用的入口点。它创建了一个 Web Worker 实例,并设置了一个输入框的 oninput 事件回调。 #[wasm_bindgen] pub fn startup() { // 创建Web Worker实例 let worker_handle = Rc::new(RefCell::new(Worker::new("./worker.js").unwrap())); console::log_1(&"Created a new worker from within Wasm".into()); setup_input_oninput_callback(worker_handle); } // 定义 setup_input_oninput_callback 函数 // 这个函数设置了一个回调函数,当用户在输入框中输入时触发。它读取输入框的值,尝试将其解析为整数,并将该整数发送到 Web Worker。 // 如果解析失败,它会清空结果显示字段。 fn setup_input_oninput_callback(worker: Rc< RefCell< web_sys::Worker >>) { let document = web_sys::window().unwrap().document().unwrap(); // #[allow(unused_assignments)] 属性被用来告诉编译器忽略未使用的赋值警告。这样,即使value变量被赋值后没有被使用,编译器也不会发出警告。 #[allow(unused_assignments)] let mut persistent_callback_handle = get_on_msg_callback(); let callback = Closure::new(move || { console::log_1(&"oninput callback triggered".into()); let document = web_sys::window().unwrap().document().unwrap(); let input_field = document .get_element_by_id("inputNumber") .expect("#inputNumber should exist"); let input_field = input_field .dyn_ref::< HtmlInputElement >() .expect("#inputNumber should be a HtmlInputElement"); match input_field.value().parse::< i32 >() { Ok(number) => { // 代码中的 Web Worker 交互包括创建 Worker 实例、发送消息给 Worker (post_message),以及设置 Worker 的 onmessage 事件处理器来接收 Worker 的响应。 let worker_handle = &*worker.borrow(); let _ = worker_handle.post_message(&number.into()); persistent_callback_handle = get_on_msg_callback(); worker_handle .set_onmessage(Some(persistent_callback_handle.as_ref().unchecked_ref())); } Err(_) => { document .get_element_by_id("resultField") .expect("#resultField should exist") .dyn_ref::< HtmlElement >() .expect("#resultField should be a HtmlInputElement") .set_inner_text(""); } } }); document .get_element_by_id("inputNumber") .expect("#inputNumber should exist") .dyn_ref::< HtmlInputElement >() .expect("#inputNumber should be a HtmlInputElement") .set_oninput(Some(callback.as_ref().unchecked_ref())); // forget 方法用于防止 Rust 清理闭包,因为闭包将由 JavaScript 管理。 callback.forget(); } // 定义 get_on_msg_callback 函数 // 这个函数创建了一个闭包,用于处理从 Web Worker 返回的消息。 // 它接收一个 MessageEvent,从中提取数据,并根据数据是 true 还是 false 来更新页面上的结果显示字段,显示 "even" 或 "odd"。 fn get_on_msg_callback() -> Closure< dyn FnMut(MessageEvent) > { Closure::new(move |event: MessageEvent| { console::log_2(&"Received response: ".into(), &event.data()); let result = match event.data().as_bool().unwrap() { true => "even", false => "odd", }; let document = web_sys::window().unwrap().document().unwrap(); document .get_element_by_id("resultField") .expect("#resultField should exist") .dyn_ref::< HtmlElement >() .expect("#resultField should be a HtmlInputElement") .set_inner_text(result); }) }
注意事项:
•Closure::new 和 Closure::forget 用于创建和管理 Rust 和 JavaScript 之间的闭包。
•Rc> 用于共享对 Worker 的可变引用,允许在多个地方修改 Worker 的状态。
•wasm_bindgen 宏用于将 Rust 代码暴露给 JavaScript,使得 JavaScript 可以调用 Rust 函数。
5、构建项目: 使用wasm-pack构建项目,生成可以在Web环境中运行的WebAssembly包。
wasm-pack build --target no-modules
--target 后面可以跟的参数,如下图
6、在Web页面中使用: 创建一个HTML文件,并在其中引入生成的.wasm文件。
index.html如下:
< html > < head > < meta content="text/html;charset=utf-8" http-equiv="Content-Type" / > < link rel="stylesheet" href="style.css" > < /head >
< h1 >与Wasm Web Worker 交互< /h1 > < input type="text" id="inputNumber" >
< script src='https://www.elecfans.com/images/chaijie_default.png' >< /script > < script src="https://www.elecfans.com/images/chaijie_default.png" >< /script > < /body > < /html >
// index.js // `#[wasm_bindgen]` const {startup} = wasm_bindgen; async function run_wasm() { // 加载 Wasm 文件 // 在`index.html`里导入了`wasm_bindgen` await wasm_bindgen(); console.log('index.js loaded'); // 运行入口方法 // 创建worker实例 startup(); } run_wasm();
// worker.js // 这段代码包含 Web Worker 的实现细节, worker.js 接收到数字后,会判断它是否为偶数,并将结果发送回主线程。 importScripts('https://www.elecfans.com/images/chaijie_default.png'); console.log('Initializing worker') // In the worker, we have a different struct that we want to use as in // `index.js`. const {NumberEval} = wasm_bindgen; async function init_wasm_in_worker() { // Load the Wasm file by awaiting the Promise returned by `wasm_bindgen`. await wasm_bindgen('./pkg/wasm_in_web_worker_bg.wasm'); // Create a new object of the `NumberEval` struct. var num_eval = NumberEval.new(); // Set callback to handle messages passed to the worker. self.onmessage = async event => { // By using methods of a struct as reaction to messages passed to the // worker, we can preserve our state between messages. var worker_result = num_eval.is_even(event.data); // Send response back to be handled by callback in main thread. self.postMessage(worker_result); }; }; init_wasm_in_worker();
7、启动一个HTTP服务器: 你可以使用任何HTTP服务器来提供你的页面和WebAssembly模块。例如,如果你已经安装了Python,可以使用以下命令:
python3 -m http.server
然后在浏览器中打开http://localhost:8000,你应该能看到【Rust 和 WebAssembly 与现有的 JavaScript 工具集成】的网站。
8、整体文件目录如下:
六、其他相关工具:
1、wasm-bindgen
wasm-bindgen促进 Rust 和 JavaScript 之间的高级交互。它允许将 JavaScript 内容导入 Rust 并将 Rust 内容导出到 JavaScript。
2、wasm-bindgen-futures
wasm-bindgen-futuresPromise是连接 JavaScript和 Rust 的桥梁Future。它可以双向转换,在 Rust 中处理异步任务时非常有用,并允许与 DOM 事件和 I/O 操作进行交互。
3、js-sys
所有 JavaScript 全局类型和方法的原始wasm-bindgen导入,例如Object、等。这些 APIFunction可eval在所有标准 ECMAScript 环境中移植,而不仅仅是 Web,例如 Node.js。
4、web-sys
wasm-bindgen所有 Web API 的原始导入,例如 DOM 操作setTimeout、Web GL、Web Audio 等。
七、应用场景:
JavaScript 与 Rust 和 WebAssembly (Wasm) 的集成可以应用于多种场景,特别是在需要高性能和/或低级系统访问的情况下。以下是一些具体的应用场景:
1.性能密集型任务:对于需要大量计算的任务,如图像或视频处理、大数据分析、复杂算法(如机器学习模型的推断)等,Rust 生成的 WebAssembly 可以提供比纯 JavaScript 更好的性能。
2.游戏开发:WebAssembly 可以使开发者将现有的高性能游戏引擎(如Unity 或 Unreal Engine)移植到网页上,或者使用 Rust 编写自定义的游戏逻辑,以实现接近原生的性能。
3.加密和安全:Rust 提供了内存安全的保证,这对于加密算法和安全相关的代码非常重要。使用 Rust 编写的 WebAssembly 模块可以在客户端执行加密操作,而不必担心内存安全漏洞。
4.物联网 (IoT) 和边缘计算:Rust 和 WebAssembly 的组合可以用于在浏览器之外的环境中运行,例如在支持 WebAssembly 的 IoT 设备或边缘计算节点上。这允许开发者在这些环境中运行高性能的应用程序。
5.桌面应用:通过技术如 Electron 或 Tauri,开发者可以创建跨平台的桌面应用。Rust 和 WebAssembly 可以用于这些应用中的性能关键部分,以提高整体性能。
6.文件压缩和解压缩:文件处理操作,如压缩和解压缩,可以通过 Rust 和 WebAssembly 实现,以提高处理速度并减少在客户端执行这些操作的时间。
7.实时通信:对于需要低延迟的实时通信应用,如在线协作工具、实时游戏等,Rust 和 WebAssembly 可以提供必要的性能优势。
8.自定义渲染器:对于需要自定义渲染管线的应用,如图形编辑器或数据可视化工具,Rust 和 WebAssembly 可以提供更接近硬件的控制和更好的性能。
9.移植现有的 Rust 库:许多有用的 Rust 库可以被编译成 WebAssembly,使得它们可以在网页应用中使用,扩展了 JavaScript 的能力。
10.替代插件:对于传统上依赖于 NPAPI 插件(如 Flash 或 Java Applets)的功能,WebAssembly 提供了一个更安全、更现代的替代方案。
在集成 Rust 和 WebAssembly 到 JavaScript 项目中时,通常会使用 JavaScript 作为“胶水代码”,处理 DOM 操作、网络请求等,而将计算密集型或需要优化性能的部分交给 Rust 编写的 WebAssembly 模块处理。这种方式可以结合 Rust 的性能和安全性以及 JavaScript 的灵活性和生态系统。
八、小结:
1、主要是为大家解决问题,多提供一种思路、方式、方法;
2、Rust 和 WebAssembly与JavaScript集成优势:
▪性能提升: Rust 编译到 WebAssembly 可以提供接近原生的性能,特别是在计算密集型任务中,这通常比 JavaScript 执行得更快。
▪类型安全: Rust 是一种静态类型语言,提供了编译时类型检查,这有助于减少运行时错误。
▪内存安全: Rust 的所有权和借用机制确保了内存安全,没有垃圾收集器的开销,这在 WebAssembly 中同样适用。
▪并发编程: Rust 的并发编程模型比 JavaScript 的并发模型(基于事件循环和回调)更为强大和灵活。
▪现代工具链:Rust 的`cargo`工具链和`wasm-pack`等工具提供了强大的依赖管理和构建工具。
▪生态系统:Rust 的生态系统正在快速增长,提供了大量的库和框架。
▪跨平台兼容性:WebAssembly 是跨平台的,可以在所有主流浏览器上运行。
3、Rust 和 WebAssembly与JavaScript集成劣势:
▪学习曲线:对于熟悉 JavaScript 的开发者来说,Rust 的学习曲线可能会比较陡峭。
▪工具集成:尽管 Rust 和 WebAssembly 的工具正在改进,但它们与现有的 JavaScript 工具和生态系统(如 npm, webpack 等)的集成可能不如纯 JavaScript 项目那样无缝。
▪启动时间和文件大小:WebAssembly 模块可能需要额外的加载时间,尤其是当模块很大时。虽然 Wasm 文件通常比等效的 JavaScript 文件小,但是需要额外的解析和编译时间。
▪DOM 和 Web API 交互:直接从 Rust/WebAssembly 与 DOM 进行交互比从 JavaScript 进行交互更复杂,通常需要通过 JavaScript 中间层或使用像`web-sys`这样的库。
▪调试支持:虽然 WebAssembly 的调试工具在不断改进,但它们通常不如 JavaScript 的调试工具成熟和易用。
▪社区和资源:JavaScript 拥有一个庞大的社区和大量的资源,而 Rust 和 WebAssembly 相对较新,社区和资源可能没有那么丰富。
▪浏览器兼容性:虽然 WebAssembly 在所有现代浏览器上都得到了支持,但在一些旧的浏览器或者某些移动设备上可能不被支持。
总的来说,Rust 和 WebAssembly 在性能和安全性方面提供了显著的优势,但在易用性、工具集成和社区支持方面可能存在一些挑战。对于需要高性能计算的应用程序,或者那些对安全性有严格要求的项目,使用 Rust 和 WebAssembly 可能是一个很好的选择。然而,对于需要快速开发和广泛社区支持的项目,纯JavaScript 解决方案可能更加合适。
欢迎大家留言或私信我,我们共同探讨、学习,并且相互提供宝贵的反馈和建议。
- 随机文章
- 热门文章
- 热评文章
- 戈兰高地遇袭,拜登政府担心以色列和真主党爆发全面战争?
- 第一套房脑子一热,弄了个全屋瓷砖上墙,第二套房我发誓不会再做
- 昂科烧录器支持HolyChip芯圣电子的8位触摸微控制器HC88T3661
- 要想保持头部的清爽,就选择清扬男士控油清爽洗发露!因为他是专门为男士设计的洗发露!
- 中新健康|国家医保局:全国政策范围内住院医疗费用基金支付比例达70%左右
- 昂科烧录器支持TI德州仪器的32位微控制器TMS320F28032
- 蛋炒饭是先炒蛋还是先炒饭?何必分先后,一起炒就OK啦,黄金蛋炒饭,你值得拥有~
- 智慧水利解决方案完整版(附:智慧水利项目所需设备文档)
- Profibus DP主站转EtherCAT从站协议网关(JM-DPM-ECT)
- 儿童异物卡喉怎么办?家长必学,关键时刻能救命!|健康过暑假
- 高功率直流快速充电解决方案
- 辽宁吉林云南等地降雨仍较强 南方地区高温天气发展
- 青海首例“5G+机器人”远程操作髌骨内侧支持带重建术成功实施
- 1防风防寒!北京今天晴朗伴大风寒意十足 周末将迎小幅升温
- 2新手如何开始跑步?
- 3在中超联赛赛场北京成都球迷高呼:北京加油,成都雄起
- 4西南地区持续阴雨天气 华北黄淮等地大气扩散条件逐步转差
- 5大雾黄色预警:京津冀等8省市部分地区有大雾 局地强浓雾
- 6春晚、哪吒带动文化经济高燃开年,中国IP大有可为!
- 7课间延长、学籍管理新规……新学期,这些变化与你有关
- 8超80亿美元!中芯国际2024年营收创历史新高,净利润减两成
- 9hyper 内存,Hyper内存:如何监控与优化hyper-v虚拟机的内存使用
- 10洞察:人形机器人传感器产业链概览
- 11敏芯股份营收暴涨超35% MEMS传感器业务全面复苏
- 12年底冲刺,家电换新求“新”更求“质”
- 13AI智算驱动光模块上市公司业绩飙涨!新易盛净利涨3倍
- 1Rab 睿坡 Xenon 2.0 男子保暖夹克
- 2中国移动 流量福利活动 免费领4GB流量券
- 3百亿补贴:Lenovo 联想 小新Pro 16 2022款 锐龙版 16英寸笔记本电脑(R7-6800H、16GB、512GB)
- 4China unicom 中国联通 爆款卡 20年29元月租(160G通用流量+100分钟通话+自主激活+送靓号)返10元红包
- 5联想拯救者 R7000 游戏本增配,搭最新 AMD 锐龙7 8745H + RTX 40 独显6699元起
- 6中国电信:汛期地区欠费用户也能用天通卫星服务
- 7全马跑者推荐,南卡Runner Pro5,跑步必备,骨传导音质天花板,潜艇级防水技术,值得入手
- 8清爽宅家~有台神仙茶吧机~你就会爱上喝水
- 9“宝宝巴士”极氪MIX最新官图公布,预计下半年上市
- 10胶囊收纳难?纠结喝点啥?一个抓娃娃机搞定所有难题!
- 11给大家种草一款护眼神器 米家防蓝光眼镜Pro 复古好看性价比高
- 12广西“八大米粉”排行,螺蛳粉垫底,游客:本地人果然更懂米粉
- 13泡椒鸡爪的家常做法分享