前言

笔者好久没有玩过Webassemly,玩一玩。

应该搞什么?不能搞太复杂了,类似于前面的

syn和quote的简单使用——生成结构体-CSDN博客https://blog.csdn.net/qq_63401240/article/details/150609865?spm=1001.2014.3001.5502

  1. 前端传入字符串
  2. 生成结构体
  3. 编译成wasm
  4. 前端调用

前面的函数宏需要的字符串,而不是字面量,而且前面函数宏提前编译,不能动态使用

因此,需要写一下新的宏,不能搞太复杂了。

需要先重构前面通过字符串生成结构体的宏。行,搞事情。

正文

重构生成结构体的宏

确定输入参数

希望如下代码能运行

fn main() {
    build_struct!(Student, [(id, i32), (name, String)]);
    let s = Student::new(1, "Alice".into());
    println!("{:?}", s);
}

打印结果如下

Student { id: 1, name: "Alice" }

前面使用的是过程宏中的函数宏,这里就不能使用了,既然如下,就使用声明宏

同理,对输入进行观察

  1. 第一个参数是一个标识符ident
  2. 第二个参数是一个中括号,里面多个类似重复的元素
  3. 每一个元素用小括号包裹,其中有两个参数,第一个参数也是标识符——ident,第二个参数是类型ty。
  4. 需要为结构体实现new方法

上面四点观察,确定这个声明宏的参数如下。

macro_rules! build_struct {
    (
        $name:ident,
        [$(($field:ident,$type:ty)),*]
    ) => {
        
    };
}

把$(..),*表示前面括号里的模式按逗号分隔重复 0 次或多次,每次重复元素都用逗号 ,连接起来

经典操作。

生成结构体

简单来说,代码如下

        #[derive(Debug)]
        pub struct $name {
            $(pub $field: $type),*
        }

重复循环就可以了

实现new方法

        impl $name{
            pub fn new($($field: $type),*)->Self{
                Self{
                    $($field),*
                }
            }
        }

整体上就是使用了上面的$(..),*语法不必多说。

build_struct宏的代码如下

macro_rules! build_struct {
    (
        $name:ident,
        [$(($field:ident,$type:ty)),*]
    ) => {
        #[derive(Debug)]
        pub struct $name {
            $(pub $field: $type),*
        }
        impl $name{
            pub fn new($($field: $type),*)->Self{
                Self{
                    $($field),*
                }
            }
        }
    };
}

运行没问题。很好。

结构体被编译后在前端运行

需要使用wasm-bindgen这个crate的一些宏,在此之前,先建立一个项目

  1. 笔者使用pnpm create vite创建一个react项目
  2. 进入react项目,使用rsw new <webassembly项目名>创建一个webassembly项目
  3. 在根目录下使用rsw watch启动项目

参考

rwasm/vite-plugin-rsw - Vite 中文文档https://www.viterc.cn/en/vite-plugin-rsw.html其中rsw.toml文件中的关键内容如下

cli = "pnpm"
[new]
using = "rsw"
[[crates]]
name = "parse-wasm"

完成之后,进入webassembly项目,src/lib.rs中,删除初始代码,写下如下代码

#[wasm_bindgen]
pub struct Student {
    pub id: i32,
    pub name: String,
}
#[wasm_bindgen]
impl Student {
    #[wasm_bindgen(constructor)]
    pub fn new(id: i32, name: String) -> Student {
        Student {
            id,
            name
        }
    }
}

前端代码

import {useEffect} from "react"
import init, {Student} from "../parse-wasm/pkg"
import './App.css'

function App() {
  useEffect(() => {
    (async () => {
      await init();
      const s=new Student(1,"张三")
      console.log(s)
      console.log(s.name)
      console.log(s.id)
    })()
  }, [])

  return null   // 暂时什么都不渲染
}


export default App

前后端意思不必细说,但是报错了

error[E0277]: the trait bound `String: std::marker::Copy` is not satisfied                                                                                                                                  
 --> src\lib.rs:6:15
  |
3 | #[wasm_bindgen]
  | --------------- in this procedural macro expansion
...
6 |     pub name: String,
  |               ^^^^^^ the trait `std::marker::Copy` is not implemented for `String`
  |
note: required by a bound in `__wbg_get_student_name::assert_copy`
 --> src\lib.rs:3:1
  |
3 | #[wasm_bindgen]
  | ^^^^^^^^^^^^^^^ required by this bound in `assert_copy`
  = note: this error originates in the derive macro `::wasm_bindgen::__rt::BindgenedStruct` (in Nightly builds, run with -Z macro-backtrace for more info)

简单地说,报错的原因是String这个类型没有实现Copy这个trait,

但是,可以发现__wbg_get_student_name,这个东西,为name这个字段实现了get方法。

这内部如何实现的,很有可能wasm_bindgen这个属性宏有关。总之,报错了

如果说,去掉pub这个关键字,即

#[wasm_bindgen]
pub struct Student {
    pub id: i32,
    name: String,
}

编译成功了,直接给出前端运行的结果,如下

无法获取到name这个字段了。

因此,可以认为

对于pub字段wasm_bindgen会为其生成get方法。

这里,因为String没有实现Copy,因此,报错了。

String in std::string - Rusthttps://doc.rust-lang.org/std/string/struct.String.html可以去掉pub,然后自己生成,也可以使用另一种方法

#[wasm_bindgen]
pub struct Student {
    pub id: i32,
    #[wasm_bindgen(getter_with_clone)]
    pub name: String,
}

为属性宏添加一个元数据getter_with_clone,笔者选择这种方法,应该还有其他方法。

在前端,src/App.tsx文件中,代码如下

运行,结果如下

前端,可以使用rust的“结构体”

使用声明宏生成结构体

结合前面所述,代码如下

use wasm_bindgen::prelude::*;

macro_rules! build_struct {
    (
        $name:ident,
        [$(($field:ident, $type:ty)),*]
    ) => {
        #[wasm_bindgen]
        #[derive(Debug, Clone)]
        pub struct $name {
            $(#[wasm_bindgen(getter_with_clone)] pub $field: $type),*
        }

        #[wasm_bindgen]
        impl $name {
            #[wasm_bindgen(constructor)]
            pub fn new($($field: $type),*) -> Self {
                Self { $($field),* }
            }
        }
    };
}

build_struct!(Student, [(id, i32), (name, String)]);

运行没问题

前端传入字符串生成结构体

这一步无法实现,因为宏展开发生在编译期,前端传进来的字符串是运行期数据,编译期根本无法知道它的值。所以,这一步走不了,可惜了,

就这样吧!!!!!!!!!!!!!!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐