使用 rust 和 golang 体验 wasm

使用 rust 和 golang 体验 wasm

本来是来记录使用 rust 来做 wasm 的开发.但是整个过程相比 golang 体验 wasm 的开发实在是太流畅了,几乎是跟着文档走就结束了. 整体的感受是相比 golang , rust 的 wasm 的工具链非常完善.

跟着文档,几个命令就把整体的流程走通,没什么可记录的.

Rust

https://rustwasm.github.io/docs/book/

工具准备

  • yarn global add wasm-pack

https://rustwasm.github.io/wasm-pack/installer/

  • cargo install cargo-generate
  • npm install npm@latest -g

项目

创建项目: cargo generate --git https://github.com/rustwasm/wasm-pack-template

创建后设置项目名称,比如’rest_wasm_example’就完成了项目的创建,文件的 src 中包含了 lib.rs 导入了 alert函数, 导出了 greet 函数.

构建: wasm-pack build

Golang

相比 golang 需要使用 tinygo 来实现相关的开发,而且工具链没有 rust 完善. tinygo 的整个的文档就在下面的文档中,在官方代码中还有几个示例

https://tinygo.org/docs/guides/webassembly/wasm/

这个也足够我们搞清楚使用 tinygo 来完成 wasm 的开发.

感受

tinygo 相比 golang 生成出来的 wasm 文件要小得多. 但是和 rust 相比还是大了一些

在我运行生成的例子中,rust 生成的是 330 字节, tinygo 生成的 56KB.

rust 工具链完善,生成的 js 文件也更加工程化.用来和前端项目做集成会方便一些.

wasm 在浏览器里运行,有内存的限制.wasm 引擎预先分配一段内存,被称为线性内存. 在运行过程中,wasm 只能访问这段内存.着相当于一个虚拟的空间,在写程序时要时刻考虑内存占用的问题.

wasm 虽然是为浏览器出现的,但是它体现出的跨平台的特性,可能会成为一种基础底层的能力.

wasm 的出现,让浏览器作为载体,可能会出现真正的基于云的操作系统.

运行时场景思考

  • 从运行时的角度看

wasm 的运行时场景,有一些像 java 或 C# 的运行时, 比如在编译的时候会编译成为字节码,然后字节码在放到运行时里去运行.

wasm 的运行时就是 java 的 JRE 或者 c# 的 CLR . 可以通过任何的语言编译成为 wasm 后运行.java、c# 跨平台的特性变成的所有语言的特性. 可以想象 java 能做的事情, wasm 都能做.

  • 从容器的角度看

在看一些技术文章时,也有从容器的角度看看待 wasm , 假设运行时是一个安全的容器.那么 wasm 还欠缺什么呢.

程序运行时需要的资源有两种

  1. 计算资源:指的是 cpu 或者 gpu

    如何将计算资源标准化后供提供到 wasm 里面. 或者说 wasm 的运行时,如何提供底层计算资源的分配和限制.

  2. 存储资源:指的是内存

    wasm 提供了线性内存分配方式, 在 web 中是没有问题的, 但是在服务端作为密集运算的时候,可能会存在性能的问题.

  3. 输入输出端

    输入输出对于计算机语言已经有一个很好的虚拟,所有的语言都会提供一个 io 的基础库来负责输入输出. wasm 的运行时需要做一些输入输出的标准化的工作. 比如网卡(虚拟网络)、硬盘(虚拟文件)、键盘(虚拟输入输出)等等.

bookmark

在看各种运行时时,有些运行时已经做了上述的事情,比如 wasmedge 运行时为操作系统资源(例如文件系统、套接字、环境变量、进程)和内存空间提供隔离和保护.

从支持的语言看 wasmer 更多一些. wasmtime 作为字节码联盟,的到了一些大公司的支持, wasmedge 是 CNCF 的项目. 从资源的角度来看,可能 wasmtime 的发展会更好一些.

其他资源

https://wasmbyexample.dev/home.en-us.html 一些例子

https://developer.mozilla.org/zh-CN/docs/WebAssembly

https://wazero.io/ 使用 go 实现的 wasm 运行时

https://wasmtime.dev/ rust 实现的运行时,字节码联盟

https://wasmedge.org/ 运行时, CNCF 项目,

https://github.com/wasmerio/wasmer rust运行时

https://github.com/wapm-packages

用 Rust 求子串绝对值最大

用 Rust 求子串绝对值最大

这两天学习了 rust 的语法

bookmark

尝试使用 rust 来写今天的 leetcode 的每日一题

https://leetcode.cn/problems/maximum-absolute-sum-of-any-subarray

给你一个整数数组 nums 。一个子数组 [numsl, numsl+1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl + numsl+1 + ... + numsr-1 + numsr) 。

请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),并返回该 最大值 。

边查文档边写的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
impl Solution {

pub fn max_abs(a:i32,b:i32) -> i32 {

if a <0{
a= -a
}
if b<0{
b= -b
}

if a > b {
a
}else{
b
}
}

pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let mut res = 0;

let mut dp_max = vec![0;nums.length()];
let mut dp_min = vec![0;nums.length()];

for (i,num) in nums.iter().enumerate() {
if i == 0 {
dp_max[0] = *num
dp_min[0] = *num
res = Solution::max_abs(dp_max[0],dp_min[0])
}else{
let max_num = dp_max[i-1] + *num
if max_num > *num{
dp_max[i] = max_num
}else{
dp_max[i] =*num
}

let min_num = dp_min[i-1] + *num
if min_num < *num{
dp_min[i] = min_num
}else{
dp_min[i] = *num
}

max_num = Solution::max_abs(dp_max[i],dp_min[i])
res = Solution::max_abs(max_num,res)
}
}
res
}
}

编译错误提示

  • 忘记写 ;
1
2
3
4
5
6
7
error: expected `;`, found `dp_min`
--> src/main.rs:60:33
|
60 | dp_max[0] = *num
| ^ help: add `;` here
61 | dp_min[0] = *num
| ------ unexpected token
  • 获取数组长度 len 写成 length
1
2
|         let mut dp_max = vec![0;nums.length()];
| ^^^^^^ help: there is a method with a similar name: `len`
  • 未定义的结构体
1
2
34 | impl Solution {
| ^^^^^^^^ not found in this scope
  • 最后的返回没有分号
1
2
3
4
5
6
7
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
| ---------------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
87 | res;
| - help: remove this semicolon to return this value
  • 计算最大绝对值函数的所有权
1
2
3
4
5
6
7
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
| ---------------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
...
87 | res;
| - help: remove this semicolon to return this value
  • 所有权只读
1
2
3
4
5
6
7
8
70 |                 let max_num = dp_max[i-1] + *num;
| -------
| |
| first assignment to `max_num`
| help: consider making this binding mutable: `mut max_num`
...
84 | max_num = Solution::max_abs(dp_max[i],dp_min[i]);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

修改的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
struct Solution{

}


impl Solution {

pub fn max_abs(a:i32,b:i32) -> i32 {
let mut a = a ;
let mut b = b ;
if a <0{
a= -a;
}
if b<0{
b= -b;
}

if a > b {
a
}else{
b
}
}

pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let mut res = 0;

let mut dp_max = vec![0;nums.len()];
let mut dp_min = vec![0;nums.len()];

for (i,num) in nums.iter().enumerate() {
if i == 0 {
dp_max[0] = *num;
dp_min[0] = *num;
res = Solution::max_abs(dp_max[0],dp_min[0])
}else{
let mut max_num = dp_max[i-1] + *num;
if max_num > *num{
dp_max[i] = max_num;
}else{
dp_max[i] =*num;
}

let min_num = dp_min[i-1] + *num;
if min_num < *num{
dp_min[i] = min_num;
}else{
dp_min[i] = *num;
}

max_num = Solution::max_abs(dp_max[i],dp_min[i]);
res = Solution::max_abs(max_num,res);
}
}
res
}
}


fn main(){
Solution::max_absolute_sum(vec![1,-3,2,3,-4]);
}

总结

参看其他人写的代码

  • 直接使用 for i in 0..n 要比 nums.iter().enumerate() 要好一些
  • 数值型函数本身有 max min abs 函数,不需要自己写.
  • 计算时由于过程已经保存 dp 的结果也不用保存,只需要保存上一次的结果给下一次循环用即可.
  • rust 的语法确实很难描述,需要大量的实践,特别是开始写的时候变量的传递有些懵.

最终版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

//f(n) 绝对值最大 = n 的 最大值和最小值的绝对值取大
// f(n) 最大值 = f(n-1) 的最大值+n 和 n 取大
// f(n) 最小值 = f(n-1) 的最小值+n 和 n 取小

impl Solution {
pub fn max_absolute_sum(nums: Vec<i32>) -> i32 {
let n = nums.len();
let mut res = 0;

let mut dp_max = 0;
let mut dp_min = 0;

for i in 0..n {
if i == 0 {
dp_max = nums[0];
dp_min = nums[0];
res = dp_max.abs().max(dp_min.abs());
}else{

let max_num = dp_max + nums[i];
dp_max = max_num.max(nums[i]);

let min_num = dp_min + nums[i];
dp_min = min_num.min(nums[i]);

res = res.max(dp_max.abs().max(dp_min.abs()));

}
}
res
}
}

Rust 小记

编程语言是用出来的, rust 文档已经看过几遍,但是依然不得要领. 通过和golang 做类比,以下是一些记录

变量默认不可变,变量可隐藏 .

let 不可变 ,let mut 可变,相比其他语言默认为可变 . 另外就是变了可以通过重新定义的方式.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个不可变的变量
let x = 5;
// 下面的代码将会报错,因为x是不可变的
x = 10;

// 定义一个可变的变量
let mut y = 5;
// 修改y的值
y = 10;

// 重新定义一个变量z
let z = 5;
let z = z + 10; // z现在的值为15,但是它是一个新的变量

数据类型

整型、浮点型、布尔类型和字符类是基础类型,字符串切片、结构体 和 golang 大体一致.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 整型类型
let x: i32 = 5;
println!("x = {}", x);
// 浮点型类型
let y: f64 = 3.14;
println!("y = {}", y);
// 布尔类型
let is_rust_fun: bool = true;
println!("is_rust_fun = {}", is_rust_fun);
// 字符类型
let c: char = 'A';
println!("c = {}", c);
// 字符串切片类型
let s: &str = "hello, world";
println!("s = {}", s);


// 结构体类型
// 定义一个结构体
struct Person {
name: String,
age: u8,
is_male: bool,
}
let person = Person {
name: String::from("Tom"),
age: 18,
is_male: true,
};

元组看使用示例是作为返回场景,golang 的多值返回一致. 其他地方的使用和结构体区别?写法的区别?

元组可以作为函数的返回值或者作为变量的值进行赋值。元组和结构体的区别在于元组的成员没有命名,而结构体的成员是有命名的。

在使用元组时需要注意,如果元组的成员是可变的,那么整个元组也是可变的。如果需要保证元组的成员不可变,可以使用 & 符号将元组的引用作为函数的返回值。

在同组值的时候可以很方便,比如表示坐标 Point(x,y)

看上去元组只时结构体的一种特殊表示.

函数返回值为表达式. 最后一个表达式的值作为函数的返回值.

如何提前返回?

rust 也有 return 关键字, 用于提前返回 ,那最后省略 return 的用意时什么?

在 Rust 中,函数的最后一个表达式的值将自动成为函数的返回值,因此可以省略 return 关键字。

1
2
3
4
5
6
7
8
// 使用 `return` 关键字
fn add_one(x: i32) -> i32 {
return x + 1;
}
// 省略 `return` 关键字
fn add_one(x: i32) -> i32 {
x + 1
}

循环

包括了 loop while for ,和 c 语言差不多.相比来说 golang 直接使用 for 来表示循环要简洁一些.

所有权

rust 的每个值都有一个所有权, 那么 = 的意思在其他语言里面的意思是: 把某值给予某变量. rust 里的意思是,把某值的拥有权交给谁.

前者的意思是,把东西放到你的仓库

后者的意思是给你一把仓库的钥匙,而且这个钥匙只有一把(只能有一个所有者,等你离开这里,这把钥匙就销毁,而且仓库也同时清理. (离开作用域,值被丢弃)

  • 值传递给函数时,所有权会交给函数.
  • println! 宏传递的是引用,不会转移所有权

bookmark

引用与借用

引用允许在没有所有权的情况下访问变量地址. 有点像 c 语言的指针的概念.

引用能对变量做什么?

引用的变量不可变,所以尝试修改会报错.可以使用 &mut 来定义可变引用 ,这里称之为借用.

借用: 不能在同一时间多次将变量作为可变变量借用. 也就是借用只能同一时间借给一个人,再次使用,只能等前一个还回来.(钥匙只有一把,只能借一个人)

在借用的同时,不能在做引用, 不可变的引用不希望变量被意外改变.

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。

枚举

在 golang 里面通常使用常变量的方式来定义一组变量,作为枚举值. rest 不久提供了枚举,还提供了一个通用枚举 Option

从后面的错误处理上看,rust 的枚举并不是单纯的枚举,而是通过枚举来设计了语言特性,比如 match 、错误处理

vector 和 array

vector 和 array 的区别?

vector 的大小不固定,可以动态的扩容. 区别是 vecter 是分配在栈上面.

  1. 大小可变性:Array 的大小是固定的,一旦定义就不能再改变。而 Vector 的大小是可变的,可以在程序运行时动态地添加或删除元素。
  2. 分配方式:Array 在栈上分配,而 Vector 在堆上分配。因为 Vector 的大小是可变的,所以需要在堆上分配内存。而 Array 的大小是固定的,所以可以在栈上分配内存,这样可以更快地访问元素。
  3. 索引访问:Array 的元素可以使用下标直接访问,比如 arr[0]。而 Vector 的元素也可以使用下标访问,但是需要使用 get 方法,比如 vec.get(0)。这是因为 Vector 的大小是可变的,有可能访问不存在的元素,所以需要在访问时进行一些检查。
  4. 用途:Array 适用于大小固定的情况,比如存储一组固定长度的数据。而 Vector 适用于大小不确定的情况,比如读取文件的内容、网络传输等场景。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义一个数组
let arr = [1, 2, 3, 4];
// 定义一个向量
let mut vec = vec![1, 2, 3, 4];
// 访问数组的元素
println!("The first element of array is {}", arr[0]);
// 访问向量的元素
println!("The first element of vector is {}", vec[0]);
// 向向量中添加元素
vec.push(5);
// 计算数组的长度
println!("The length of array is {}", arr.len());
// 计算向量的长度
println!("The length of vector is {}", vec.len());

hashmap

hash map 用法和 golang 基本一样,需要注意一下所有权.

错误处理

rest 使用 Result<T,E> 的枚举来返回是否出现了错误,等同 golang 里面的返回 res,err := function(xxx)

Result<T,E> 定义了一些辅助方法来处理一些情况,比如

1
let greeting_file = File::open("hello.txt").unwrap();

unwrap 成功返回,失败直接 panic

expect 可以自定义错误信息.

可恢复错误: 当出现错误时,不是所有的错误都需要 panic. 所以可以将错误向上传播,使用 ? 来作为向上传播的标识符, ? 实际是一个宏,用来做 match 返回.

1
2
3
4
5
6
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};

? 给我的感觉是 golang 的 err ≠ nil 的方式相比不够优雅. 不过这都是建立在返回是枚举的基础上.

trait 类似于 golang 的 interface

(rust 的命名如果能和其他语言保持一致的话,可能更加好学一些吧,比如 match 和 switch )

智能指针

智能指针 像是为了解决对应引用问题定义的类型?

从文档来看是来源于 c++ 的概念

  • Box<T>,用于在堆上分配值

    • 解引用 Deref
    • 释放资源 Drop

    解引用可以将智能指针当普通指针来使用, 通过 deref 将引用返回

    释放资源在值离开作用域时调用(手动回收堆上的数据?)

  • Rc<T>,一个引用计数类型,其数据可以有多个所有者

    • 对一个Rc变量进行clone()时,不会将其内部的数据复制,只会增加引用计数。
    • 当一个Rc变量离开作用域被drop()时,只会减少引用计数,直到引用计数为零时,才会真正清除其拥有数据的堆内存。
    • Arc<T> : Atomic Rc 原子化的 Rc<T>,相比来说,带来线程安全和性能损耗
  • Ref<T> 和 RefMut<T>,通过 RefCell<T> 访问。( RefCell<T> 是一个在运行时而不是在编译时执行借用规则的类型)。

    • Cell 只适用于 Copy 类型,用于提供值,而 RefCell 用于提供引用
    • Cell 不会 panic,而 RefCell 会

并发

rust 提供了线程 、锁,提供了像 golang channel 的类型

async 提供了类似 golang 协程的概念.

总结

这里只是对rust 做了片面的理解, rust 所描述的没有垃圾回收,实际是任何时候,都明确变量在哪个位置被回收

定义好的变量在堆上还是在栈上也是确定的,所以回收的时机也是明确的.

所以不需要垃圾回收算法

rust 的设计上处处都体现着与其他语言的不同,变量、描述、定义,从语法上吸收了 c++ 的语法, 但依然过于复杂. 比如很多约定俗成的描述,依然换了一种描述方式,比如 match 而不是用 switch .

参考:

bookmark

bookmark