Rust 和 WebAssembly 的世界

本文探讨了Rust与WebAssembly在前端开发中的应用,介绍了Rust的类型系统和所有权模型,强调了其在安全性与性能上的优势。通过WebAssembly,Rust代码可以在浏览器中运行,与JavaScript协作提升应用性能。文章还提到了Yew框架,它是使用Rust编写的前端框架,允许开发者利用WebAssembly创建高性能的多线程UI。此外,文中还简要提及了AssemblyScript,这是一种 TypeScript的变种,用于编译成WebAssembly,降低了使用WebAssembly的门槛。最后,讨论了如何在Vue/React等框架中整合WebAssembly,展示了WebAssembly在前端开发中的广阔前景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

但是虽然可以编译,这样书写是绕不过静态类型检查的!!!!!!

相信大家发现了上面的string类型有些特殊,不是说string是"值类型"吗?为什么他又可以用引用类型来表示呢?string使用了没有所有权的特殊的引用类型slice,slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。

let s = String::from(“hello”);

let slice = &s[0…2];

let slice = &s[…2];

对于"值类型"的string

let s = “Hello, world!”;

这里 s 的类型是 &str:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str 是一个不可变引用。

悬垂引用 悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态 例如以下代码:

fn main() {

let reference_to_nothing = dangle();

}

// wrong

fn dangle() -> &String {

let s = String::from(“hello”);

&s

}

//safe

fn safe() -> String {

let s = String::from(“hello”);

s

}

因为 s 是在 dangle 函数内创建的,当 dangle 的代码执行完毕后,s 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 String,这可不对!Rust 不会允许我们这么做 总结两条规则

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。

  • 引用必须总是有效的。

到这里和JavaScript有联系的,并且基础的就分享的差不多了,隐约记得鲁迅说过,如果你对一门语言,了解了其基本的语法,能够编写对应的简单的代码来实现简单的功能,那么你就入门了。后续的包括以下部分,就先按下不表

  • Cargo : Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。类似于JS使用的npm/pnpm/yarn

  • 常见集合:Hashmap(类似于js中的map),Vector(类似于js中的数组),String

  • 错误处理:panic(Throw Error 完全阻塞了程序执行) Result(类似于warning 可以报错但是不影响程序的执行)

最后总结一下rust我认为最令人称道的两点

  1. 丰富而强大的类型系统

  2. 可信赖的所有权模型

Rust and WebAssembly

====================

上面讲了半天rust,他只是我们今天的猪脚之一,那么今天的猪脚还有哪位呢?没错,就是 WebAssembly。那么WebAssembly到底是什么呢?在说这个之前先康康JavaScript的是怎么进行编译的 这就不得不说到两种编译方式了

  • AOT: Ahead-of-Time compilation

必须是强类型语言,编译在执行之前,编译直接生成CPU能够执行的二进制文件,执行时CPU不需要做任何编译操作,直接执行,性能最佳,比如C/C++,Rust

  • JIT: Just-in-Time compilation

没有编译环节。执行时根据上下文生成二进制汇编代码,灌入CPU执行。JIT执行时,可以根据代码编译进行优化,代码运行时,不需要每次都翻译成二进制汇编代码,V8就是这样优化JavaScript性能的。

举个例子,如果使用var来声明一个变量,不使用Typescript等类型系统来限定,一个变量,在多次编译的时候得到的变量的类型可能会不一样,这就导致了每一次JavaScript在执行的时候可能都会被重新编译,这就是类型系统的重要性,不仅能减少bug的发生也可以让我们的代码跑得更快

详细的说一下这个过程也就是

  1. 代码文件会被下载下来。

  2. 然后进入Parser,Parser会把代码转化成AST(抽象语法树).

  3. 然后根据抽象语法树,Bytecode Compiler字节码编译器会生成引擎能够直接阅读、执行的字节码。

  4. 字节码进入翻译器,将字节码一行一行的翻译成效率十分高的Machine Code.

有同学可能会问:JavaScript不是可以使用Typescript进行静态类型检查吗?为什么不能在编译时编译成可执行的二进制文件呢?盲生,你发现了华点!Typescript说白了也只是给JavaScript打上了补丁,但是JavaScript还是那个JavaScript,说不定在有生之年可以看见JavaScript的整个内核被重写呢?Wasm:那我走?

回到正题,既然JavaScript的内核变化的几率不大,那我们该如何进行优化呢?一个思路就是可以直接把 C、C++、Rust等语言编译成 WebAssembly 并能在浏览器中运行,但是有一点需要注意,使用wasm并不是完全舍弃掉了JavaScript,这两者实际上是相辅相成的关系,在实际的应用场景中Rust和JavaScript往往是互相调用包来开发一个web应用。

WebAssembly是一份字节码标准,以字节码的形式依赖虚拟机在浏览器中运行。万维网联盟(W3C)2019年12月5日宣布,WebAssembly 核心规范 现在是一种正式的 Web 标准,它为 Web 发布了一种功能强大的新语言。WebAssembly 是一种安全、可移植的低级格式,能够在现代处理器(包括 Web 浏览器)中高效执行并紧凑地表示代码。它也被设计为可以与JavaScript共存,允许两者一起工作。这样说大家可能云里雾里的,那么换个方法 我们每天都在接触各种业务,那大家有没有想过从我们写下JavaScript代码开始,到底发生了什么?就只看JavaScript大致是这样一个过程:

业务代码 -> v8 解析 -> 得到编译结果(字节码) -> 线程通信 -> 通知GPU绘制 -> 渲染

那如果我们使用了WebAssembly,那又是一个什么过程呢?

业务代码 -> 编译 -> 字节码 -> 线程通信 -> 通知GPU绘制 -> 渲染

可以看出,这两个链路最大的区别就是,在第二种链路中,浏览器(V8)所得到的东西,已经是一份可以执行的字节码了,他只需要执行就完事了,而不需要使用大量的CPU来对可能很复杂的源代码来进行编译。(当然也可以使用worker 这里就不做讨论了) 但是纯纯的字节码指定是不行的,C/C++,Rust可能都有自己的一套规范,所以这就需要一套规范来整合一下,让大家都可以愉快的在浏览器中玩耍,这可以说就是WebAssembly,由他的标准可以生成后缀名为.wasm的文件,可以直接交给浏览器执行 目前主流的浏览器都已经支持了WebAssembly。除此之外 ,依照wasm的特性,个人认为或者wasm未来在多端也能有一定的用处

实战

俗话说的好,纸上得来终觉浅,绝知此事要躬行,上面简单学习了rust+wasm,那如果不实践一下那不是浪费了吗,那到底怎么实践rust+wasm呢?自己看着wasm的文档写?那指定是不行的。那怎么办呢?不要慌,今天的第三位猪脚出现了:Yew 文档在此yew中文文档简介如下Yew 是一个设计先进的 Rust 框架,目的是使用 WebAssembly 来创建多线程的前端 web 应用。

  • 基于组件的框架,可以轻松的创建交互式 UI。拥有 React 或 Elm 等框架经验的开发人员在使用 Yew 时会感到得心应手。

  • 高性能 ,前端开发者可以轻易的将工作分流至后端来减少 DOM API 的调用,从而达到异常出色的性能。

  • 支持与 JavaScript 交互 ,允许开发者使用 NPM 包,并与现有的 JavaScript 应用程序结合。

让一个yew应用跑起来分三步(确信)

  1. 创建一个二进制项目

cargo new --bin yew-app && cd yew-app

  1. 编写代码,注意要编写index.html

  2. 启动

cargo install trunk wasm-bindgen-cli

rustup target add wasm32-unknown-unknown

trunk serve

一张图简述一下wasm-bindgen的作用

组件化

页面展示的代码

use yew::prelude:😗;

enum Msg {

AddOne,

}

struct Model {

link: ComponentLink,

value: i64,

}

impl Component for Model {

type Message = Msg;

type Properties = ();

fn create(_props: Self::Properties, link: ComponentLink) -> Self {

Self {

link,

value: 0,

}

}

fn update(&mut self, msg: Self::Message) -> ShouldRender {

match msg {

Msg::AddOne => {

self.value += 1;

true

}

}

}

fn change(&mut self, _props: Self::Properties) -> ShouldRender {

false

}

fn view(&self) -> Html {

html! {

{ "tandake is a Vegetable Chicken"  }

{ self.value  }

}

}

}

fn main() {

yew::start_app::();

}

效果演示

可以看出,这里的渲染的文件来源已经是wasm了 眼尖的同学可能已经发现了上面的create,update ,change几个函数,那么他们是用来干嘛的呢?简单说一下yew中组件的生命周期:Component 特质定义了六个生命周期函数。

  • create 是一个构造函数,接收道具和ComponentLink

  • view 渲染该组件

  • update 当一个Message 被发送到该组件时被调用,实现消息传递的逻辑

  • change 重新渲染变化,优化渲染速度

  • rendered 在view 之后但在浏览器更新之前被调用一次,以区分第一次渲染和连续渲染。

  • destroy ,当一个组件被卸载并需要进行清理操作时被调用。

如果把他类比成react的类组件,那么create就是constructor构造函数,update就是相当于注册在组件内部的一些静态方法,change相当于shouldcomponentupdate,其他的生命周期也可同比

父子组件中通信

前文说到yew是基于组件的,那么父子组件该怎么进行最简单的数据通信呢?声明父组件

#[derive(Clone, PartialEq, Properties, Default)]

struct Properties {

name: String,

}

enum Message {

ChangeName(String),

}

struct Model {

link: ComponentLink 《大厂前端面试题解析+Web核心总结学习笔记+企业项目实战源码+最新高清讲解视频》无偿开源 徽信搜索公众号【编程进阶路】 ,

props: Properties,

}

impl Model {

fn change_name(&mut self, name: String) {

self.props.name = name;

}

}

impl Component for Model {

type Message = Message;

type Properties = Properties;

fn create(_props: Self::Properties, link: ComponentLink) -> Self {

Self {

link,

props: Properties {

name: “tandake”.to_string(),

},

}

}

fn update(&mut self, msg: Self::Message) -> ShouldRender {

match msg {

Message::ChangeName(name) => {

self.change_name(name);

}

};

true

}

fn change(&mut self, _props: Self::Properties) -> ShouldRender {

false

}

fn view(&self) -> Html {

html! {

{ "大家好,我是练习时长两天半的rust实习生,谭达科"  }

{"hello "}{self.props.name.clone()}

}

}

}

声明子组件

#[derive(Clone, PartialEq, Properties, Default)]

struct ButtonProperties {

onclick: Callback,

}

enum ButtonMessage {

ChangName,

}

struct Button {

props: ButtonProperties,

link: ComponentLink,

}

impl Button {

fn change_name(&mut self) {

self.props.onclick.emit(“is a vegetableChicken”.to_string());

}

}

impl Component for Button {

type Message = ButtonMessage;

type Properties = ButtonProperties;

fn create(props: Self::Properties, link: ComponentLink) -> Self {

Self { props, link }

}

fn update(&mut self, msg: Self::Message) -> bool {

match msg {

ButtonMessage::ChangName => {

self.change_name();

}

};

true

}

fn change(&mut self, props: Self::Properties) -> bool {

if self.props != props {

self.props = props;

true

} else {

false

}

}

fn view(&self) -> Html {

html! {

}

}

}

演示效果

声明一个组件需要一些什么东西呢?从上面这个简单的demo可以看出一个大概

  1. 定义属性结构

#[derive(Clone, PartialEq, Properties, Default)]

  1. 把属性附加到状态

struct Button {

props: ButtonProperties,

link: ComponentLink,

}

  1. 初始化组件的状态

fn create(props: Self::Properties, link: ComponentLink) -> Self {

Self { props, link }

}

  1. 初始化生命周期,在update中接受事件,在change中重新渲染

fn update(&mut self, msg: Self::Message) -> bool {

match msg {

ButtonMessage::ChangName => {

self.change_name();

}

};

true

}

fn change(&mut self, props: Self::Properties) -> bool {

if self.props != props {

self.props = props;

true

} else {

false

}

}

fn view(&self) -> Html {

html! {

}

}

  1. 需要交互注册自定义事件

impl Button {

fn change_name(&mut self) {

self.props.onclick.emit(“is a vegetableChicken”.to_string());

}

}

这种写法与现在我们使用的react的写法是比较类似的(当然也可以使用vue的emit的方式)

函数式组件

上诉yew的组件多多少少和类组件比较像,那么yew可不可以使用一种类似函数式组件的方法?甚至使用hooks呢?当然可以 下面我们来实现一个简单的点击计数器(效果和第一个类似,就不再赘述了)

#[derive(Properties, Clone, PartialEq)]

pub struct RenderedAtProps {

pub time: String,

}

#[function_component(App)]

fn app() -> Html {

let (counter, set_counter) = use_state(|| 0);

let onclick = {

let counter = Rc::clone(&counter);

Callback::from(move |_| set_counter(*counter + 1))

};

html! {

{ “Increment value” }

{ "Current value: " }

{ counter }

}

}

#[function_component(RenderedAt)]

pub fn rendered_at(props: &RenderedAtProps) -> Html {

html! {

{ "Rendered at: " }

{ props.time.clone() }

}

}

这样看起来是不是有react的函数式组件那味了,在使用函数组件的时候,我们也可以使用yew中自带的各种hooks,包括了但不限于以下hook钩子

  • use_state

  • use_ref

  • use_reducer

  • use_reducer_with_init

  • use_effect

  • use_effect_with_deps

Yew虽然说是一款Rust框架,但是在实际使用上与Rust相关,而与我们现在学习的知识无关的地方很少,大多的时候我们都在这里面看到vue和react中的影子,使用的成本其实并不高,就像我们使用JavaScript来开发一样,大家都知道JavaScript是基于V8的,但是我们在编程的时候不是只是关注V8来进行开发吧?这个框架也是一样,虽然基于WebAssembly和Rust,但是使用起来,会比我们想象的顺滑很多。

WebAssembly 和 Javascript

========================

上面讲了yew这个新框架,但是问题又来了,这不是还是要学习Rust吗?我不会Rust,但是我就是想用WebAssembly!我就是想用JavaScript!那怎么办呢?没事,你能想到的,大家都想到了,那下面又来了一位猪脚。

AssemblyScript:用Javascript的方式来编写WebAssembly


还记得上面在介绍rust的时候,提到过的的Rust比Typescript更加丰富的系统吗?是不是看的心痒痒?没事,Rust的类型系统的确很好,但是下一秒就是我的了,那下面再请出一位猪脚 AssemblyScript 看一句官网的描述

AssemblyScript compiles a variant of TypeScript(a typed superset of JavaScript) to WebAssembly using Binaryen,It generates lean and mean WebAssembly modules while being just an npm install away

它其实就是Typescript的变种,在Typescript的基础上进一步丰富了类型系统,并且可以编译成wasm文件执行,Typescript你不要再给我打电话啦,我怕AssemblyScript 误会 可以将其视为 TypeScript 的高级语法和 C 的低级功能的混合(没错,你可以使用AssemblyScript 来操作内存!!),上文已经说过了jit是个什么玩意儿,一个完整且严格的类型系统可以让JIT更加的迅速,既然要保证对于Jit编译时的优化,也为了WebAssembly来提供静态保证,所以有意避免JavaScript无法提前_有效_编译的动态_性_。也就是说,无类型状态,是不存在的!

// ???

var a = {}

a.prop = “hello world”

// ???

var a = new Map<string,string>()

a.set(“prop”, “hello world”)

// ???

function foo(a?) {

var b = a + 1

return b

}

// ???

function foo(a: i32 = 0): i32 {

var b = a + 1

return b

}

// ???

function foo(a: i32 | string): void {

}

// ???

function foo(a: T): void {

}

当然,目前这门语言还有许多不完善的地方,对应的生态也不成熟,这门语言的目标是想要成为一个对web开发者上手门槛极低的语言,但是同时他又是一门最终需要编译为WebAssembly的语言,这就需要它在支持目前已有的开发语言的特性的基础上,又不能在依然保有某些语言编译效率底下的特性或者是盲目的迷信二进制的路上越走越远,这可以说是这门语言的哲学,也可以说是这门语言前进的方向 具体的用法就不多说了,大家有兴趣的可以去研究一下(手动狗头)AssemblyScript官方英文文档

整合 WebAssembly+Javascript+Vite+Vue/React+Rust


其实分享到这里,看了wasm这么多骚操作之后,又想起了我们平时的开发框架Vue/React,自然而然,我就会想到:那我能不能直接在Vue/React中使用WebAssembly呢?再过分一点,我甚至把下一代打包构建工具Vite也用上?答案只有一个:可以 下面我们来实战操作一下,以Vue3为示例 使用到的技术名词

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值