ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

wasm-bindgen `experimental_generic_mono` 指南:按实例化生成 JS 绑定的泛型导入

wasm-bindgen `experimental_generic_mono` 指南:按实例化生成 JS 绑定的泛型导入 开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载导读本指南讲解 wasm-bindgen 的实验性属性#[wasm_bindgen(experimental_generic_mono)]它让单个泛型导入函数在 Rust 侧保留一个签名却在 JS 侧为每一个具体实例化分别生成一个绑定shim从而使泛型参数能以u32、String、bool等普通 Rust 类型按各自的真实线格式wire format跨过 wasm ABI。读完本文你将掌握何时该用它而非默认的类型擦除type erasure路径、如何编写 trait 约束与impl Trait参数、如何配合生命周期与类级泛型class-level generics使用以及哪些签名形态会被编译期拒绝、为什么会被拒绝。⚠️实验性特性。该属性是实验性的其行为可能在任意版本中变化甚至被移除。尤其注意受支持签名形态的集合预期会持续扩大且生成的 JS 绑定内部名称__wbindgen_generic_N并不稳定。文档 Working with wasm-bindgen Generics 中描述的类型擦除路径才是编写泛型导入的受支持方式。背景默认的泛型导入是类型擦除在进入本属性之前先理解默认行为。默认情况下一个泛型导入函数的类型参数会被擦除每一个T都作为JsValue穿过 ABI且只生成一个JS 绑定服务于所有实例化。这正是 Working with wasm-bindgen Generics 所描述的机制也是T通常必须是 JS 类型JsGeneric如Array、Promise、JsString而不能是 Rust 类型的原因——擦除意味着Arrayu32不合法必须写作ArrayNumber。从源码结构看擦除逻辑位于 crates/macro-support/src/generics.rsGenericRenameVisitor在代码生成前把泛型参数替换为默认的具象类型未指定默认时替换为JsValue。该文件中的测试如test_generic_args_to_concrete印证了PromiseT→PromiseString这类替换行为。experimental_generic_mono做什么一个签名每个实例化一个绑定experimental_generic_mono让单个导入脱离擦除路径。它不再生成一个擦除绑定而是为每次单态化monomorphisation生成一个绑定每个绑定都有自己独立的描述符descriptor因此参数与返回值都按其具象类型进行编组use wasm_bindgen::prelude::*; #[wasm_bindgen] extern C { #[wasm_bindgen(js_namespace console, experimental_generic_mono)] fn logT(value: T); } log(42u32); // crosses as a number log(hello); // crosses as a string log(true); // crosses as a boolean上面三次调用各有一个独立的 JS shim。因为没有任何东西被打包进JsValueT可以是普通 Rust 类型——u32、f64、bool、String——这是擦除路径不允许的。仓库的端到端参考测试 crates/cli/tests/reference/generic-import.rs 完整固定了这条路径的产出注释明确说明每个具体实例化由描述符解释器发现并绑定到各自制造的__wbindgen_generic_*JS shim而非把类型参数擦除为JsValue。例如log_generic(1u32)与log_generic(2.0f64)、log_generic(String::from(three))、log_generic(four)分别触发不同 shim——其中str与String是同一 JS 字符串背后的两种不同线协议借用的(ptr, len)对 vs. 需要 shim 释放的拥有型缓冲。什么时候用它当你希望一个 Rust 签名服务多个 Rust 类型并且关心它们如何编组时用experimental_generic_mono。当你在建模JS 泛型容器ArrayT、PromiseT希望所有元素类型共享单个绑定、只有 Rust 类型检查器关心元素类型时用默认擦除路径。注意这个选择是关于元素编组方式的与类本身是否泛型无关experimental_generic_mono支持泛型导入类型包括作为方法接收者或构造器返回值见下文 类级泛型。问题只在于每个元素类型一个 shimper-mono还是所有元素类型共享一个 shim擦除。权衡点是代码体积每次实例化产生一个 JS shim 和一个描述符。一个在十几个类型上实例化的泛型导入会产出十几个 shim所以当具体编组方式无关紧要时应优先擦除。两种路径的对比如下源自 guide/src/reference/working-with-generics.md 的Choosing between the two小节类型擦除默认experimental_generic_mono生成的 JS 绑定一个所有实例化共享每次单态化一个T如何穿过 ABI打包为JsValue经ErasableGeneric按具象类型u32为数、String为字符串T可以是什么JS 类型JsGenericArray、Promise…wasm-bindgen 能编组的任何类型含 Rust 原生类型泛型导入类型支持——type ListNodeT仅函数级类型参数代码体积恒定随实例化数量增长Trait 约束声明即契约你声明的约束是导入契约的一部分。它们会被带到生成的包装函数wrapper上因此调用方必须满足它们也会到达生成的 shim——这意味着 shim 签名可以从受约束的参数上投影关联类型projection。内联约束、where谓词和更高阶谓词higher-ranked predicates都可用#[wasm_bindgen] extern C { #[wasm_bindgen(experimental_generic_mono)] fn sum_itemsT(items: T) - f64 where T: IntoIteratorItem u32; #[wasm_bindgen(experimental_generic_mono)] fn doubleT(value: T) - T where fora a T: core::ops::Adda T, Output T; }约束只限制导入可以被什么类型调用它本身无法让某个类型变得可编组。把更高阶约束与T参数组合是一种常见的能编译但永远无法被调用的写法T额外要求引用存在IntoWasmAbi实现而这仅对JsValue与导入的 JS 类型存在所以诸如fora a T: IntoIteratorItem a u32的约束没有任何类型能同时满足。参见 关于T参数的说明。宽松约束是例外T: ?Sized在任何 wasm-bindgen 泛型无论擦除还是逐单态化上都不支持会报unsupported in wasm-bindgen generics。从源码看约束的传递逻辑在 crates/macro-support/src/generics.rs 的generic_bounds中内联约束会被改造成where谓词因为生成的 item 不一定有参数列表槽位来承载它们被提升到impl头部的参数尤其如此内联生命周期约束如a: b同理。UI 测试 crates/macro/ui-tests/generic-per-mono-bounds.rs 固定了内联约束、where谓词以及IntoIteratorItem String这类带关联类型绑定约束的违反诊断。字符串约束JsStringLike对于接收 JS 字符串的导入实验性的wasm_bindgen::JsStringLike标记 trait 直接点名字符串形态集合use wasm_bindgen::JsStringLike; #[wasm_bindgen] extern C { #[wasm_bindgen(experimental_generic_mono)] fn set_titleT: JsStringLike(title: T); }JsStringLike为String、str、js_sys::JsString与js_sys::JsString实现因此调用方可以传递它手上已有的任意形态每个单态化都以该形态的原生线格式跨越——Rust 字符串走 UTF-8 缓冲、JsString走句柄——最终都作为 JS 字符串值到达。可空位置OptionT在同一约束下也有效因为该 trait 以OptionIntoWasmAbi作为 supertrait。trait 被密封为恰好这些字符串形态密封 trait 见 src/rt/marker.rs 的JsStringLikeSealed。与experimental_generic_mono本身一样该 trait 是实验性的可能随特性稳定而变化。impl Trait参数参数位置的impl Trait受支持。在任何其他代码生成运行之前它会被**脱糖desugar**为一个携带相同约束的合成具名类型参数因此它的单态化方式与你亲自命名的类型参数完全相同——两者可互换#[wasm_bindgen] extern C { #[wasm_bindgen(js_namespace console, experimental_generic_mono)] fn log(value: impl core::fmt::Debug); } log(42u32); log(hello);等价于书写fn logT: core::fmt::Debug(value: T)。这也意味着一个函数可以在表面上没有类型参数的情况下拥有类型参数即使impl Trait从不出现在函数自身的泛型参数列表中它也算作至少一个类型参数要求的一部分。impl Trait可以与具名类型参数混用、嵌套在另一个类型内部Vecimpl Trait、并且可以重复出现——每次出现都有自己的合成参数#[wasm_bindgen] extern C { #[wasm_bindgen(experimental_generic_mono)] fn mixT(label: T, values: Vecimpl Clone); }合成参数上的约束被严格执行如同你手写它们一样违反约束的调用方会得到指向声明中impl Trait的诊断与显式约束相同。源码实现位于 crates/macro-support/src/generics.rs 的ImplTraitDesugar访问器与desugar_impl_trait函数它把每个impl Trait含嵌套如Vecimpl Trait重写为带__WasmBindgenImplTrait{n}名字的合成具名类型参数并把impl Trait自带的约束带上。参考测试 crates/cli/tests/reference/generic-import.rs 中log_impl_trait、mix_impl_trait、log_nested_impl_trait均固定了这条路径的输出形态。生命周期参数函数上的生命周期参数受支持包括生命周期约束T: a与生命周期 outlives 谓词a: b#[wasm_bindgen] extern C { #[wasm_bindgen(experimental_generic_mono)] fn log_refa, T(value: a T); }方法接收者上的生命周期同样可行并把接收者的借用与调用的其余部分绑定#[wasm_bindgen] extern C { type Widget; #[wasm_bindgen(method, experimental_generic_mono)] fn seta, T(this: a Widget, value: a T); }生命周期不携带任何运行时信息——它们在值跨过 wasm ABI 之前就被擦除了——所以这不会施加超出普通 Rust 对签名已有要求的限制。属于类本身的生命周期也可用参见 类级泛型。源码佐证staticize_lifetimes与used_lifetimes_in_typecrates/macro-support/src/generics.rs负责在生成具象 ABI 类型时把外层生命周期替换为static、并分析类型中使用的生命周期move_lifetime_bounds_to_where则把内联生命周期约束移入where子句。编译通过测试 crates/macro/pass-tests/generic-per-mono-class-generics.rs 中TwoLifetimesa, b的例子特意以与接收者参数列表相反的顺序声明防止生成implK, V PairV, K式的错位。类级泛型类级泛型指函数的某个类型或生命周期参数同时也参数化了接收者/返回类类型本身而不是仅出现在普通参数或返回位置。这正是贯穿js-sys的形态ArrayT、MapK, V、PromiseT……导入的类型用其自身泛型参数声明而返回该类的构造器、方法或静态方法把自身某个泛型与它绑定#[wasm_bindgen] extern C { type HolderT; #[wasm_bindgen(constructor, experimental_generic_mono)] fn newT(value: T) - HolderT; #[wasm_bindgen(method, experimental_generic_mono)] fn getT(this: HolderT) - T; } let holder Holder::new(42u32); let value: u32 holder.get();函数自身被类的参数列表所使用的那个类型参数上面的HolderT中的T会被**提升hoist**出包装函数的参数列表改放到生成的impl块头部——构造器与get上方的impl变成implT HolderT而非裸的impl Holder。不属于类自身参数列表的函数参数普通非提升类型参数或仅用于参数/返回位置的参数仍留在函数上因此两类参数可以在单个签名中共存#[wasm_bindgen] extern C { type HolderT; // T is hoisted (it parameterises the receiver); U stays on combine. #[wasm_bindgen(method, experimental_generic_mono)] fn combineT, U(this: HolderT, other: U); }属于类的生命周期以同样方式工作可单独使用或与提升的类型参数并用#[wasm_bindgen] extern C { type LifetimeHoldera; #[wasm_bindgen(method, experimental_generic_mono)] fn geta, T(this: a LifetimeHoldera) - T; type LtHoldera, T; #[wasm_bindgen(method, experimental_generic_mono, js_name get)] fn lt_geta, T(this: a LtHoldera, T) - T; }这与构造器及自返回静态方法如static_method_of Holder形态的组合方式与普通实例方法的组合方式相同镜像了js-sys中Array::new/Array::of返回ArrayT的做法。不返回类的非构造器静态方法则完全不与类参数绑定按类的参数默认值绑定与擦除路径完全一致。测试证据编译通过测试 crates/macro/pass-tests/generic-per-mono-class-generics.rs 覆盖了构造器、自返回静态方法镜像Array::of、实例方法镜像Array::at/Array::get、提升与非提升参数混合combineT, U、全具象类参数Concreteu32, String此时无物可提升方法落在impl Concreteu32, String、类级生命周期LifetimeHoldera、多生命周期TwoLifetimesb, a、生命周期与类型参数同时提升LtHoldera, T、where子句约束boxed_where_boundT: Clone乃至 shim 上的关联类型投影boxed_where_projection/boxed_inline_projection需要把约束复述到 shim 自己的where子句否则生成代码会触发 E0220等场景端到端参考 crates/cli/tests/reference/generic-import.rs 的Holder/LifetimeHolder部分固定了最终 JS/wasm 输出。什么可以被提升函数的泛型被提升到生成的impl块自身头部因此类类型的每个泛型参数必须是该头部能命名、且 self 类型随后能确定的东西。每个参数可以是函数的一个泛型参数裸用HolderT或以便于确定它的方式组合HolderOptionT函数的一个生命周期参数a Holdera, T具象值Holderu32。对它没有可提升的东西——按原样重新输出因此方法落在impl Holderu32上仅对该类实例化存在。它们可在同一个参数列表内自由混合Pairu32, T产生implT Pairu32, T。以下形态会被预先拒绝而不是留待 rustc 对生成代码报出令人困惑的错误impl 类参数提及参数但不确定它HolderT::Assoc或static_method_of HolderT::Assoc。提升T会使其不被 self 类型约束。省略生命周期参数Holder_, T它无法在生成的impl头部声明。具象static生命周期受支持任何其他生命周期请改为函数的命名参数fn fa, T(this: a Holdera, T)。返回HolderT::Assoc的构造器或推断自返回静态方法不会被拒绝其返回参数无法确定T因此该方法留在导入类的默认特化上而非被提升。对应的拒绝测试见 crates/macro/ui-tests/generic-per-mono-unsupported.rsnon_constraining_class_argHolderT::ItemE0207、elided_class_lifetimeLtHolder_, T、以及需要显式提供生命周期的裸static_method_of StaticLifetimeHolder。源码中约束位置分析由 crates/macro-support/src/generics.rs 的args_are_constraining_for/type_is_constraining_for实现它镜像了 rustc 对impl块类型参数的 E0207 规则。回调参数顶层裸dyn Fn(...)或mut dyn FnMut(...)回调可以在其自身输入与返回类型中使用类型参数。这是带类型js-sysAPI如Array::map使用的形态#[wasm_bindgen] extern C { type ArrayT; #[wasm_bindgen(method, experimental_generic_mono)] fn mapT, U( this: ArrayT, callback: mut dyn FnMut(T, u32, ArrayT) - U, ) - ArrayU; }公开包装函数通过impl Fn或impl FnMut接收普通 Rust 闭包与非泛型导入路径一致。每个单态化描述回调的具体 ABI 参数与返回类型。泛型回调的输入与返回必须是拥有类型。借用形式如FnMut(T)与更高阶形式如fora FnMut(a T)会被拒绝。回调还必须是上图所示的顶层参数形态嵌套在其他类型中的回调 trait 对象不会获得此降级lowering。与其他属性的组合experimental_generic_mono可以与常规导入属性组合——method、static_method_of、constructor、getter、setter、structural、final、indexing_getter、indexing_setter、indexing_deleter、js_namespace、js_name、catch、variadic与slice_to_array——且生成的 JS 绑定形态与等价非泛型导入完全相同。两个不组合的属性是assert_no_shim与reexport两者都会被拒绝见 不支持的形态。async受支持按通常方式返回 future#[wasm_bindgen] extern C { #[wasm_bindgen(experimental_generic_mono)] async fn round_tripT(value: T) - T; }参考测试 crates/cli/tests/reference/generic-import.rs 覆盖了大量组合constructorWidget::new、methodset、set_impl_trait、attach、static_method_ofWidget::of、getter/settervalue/set_value、structuralgetter/settertag/set_tag、finalgetterkind、indexing_getter/setter/deleter、catchtry_log、try_get、variadicvariadic_log、slice_to_arraylog_slice等、asyncasync_identity、async_count、async_try以及js_namespace含[a, b]深层命名空间。应用到整个块experimental_generic_mono也可以放在extern C块上块内每个函数都会继承它#[wasm_bindgen(experimental_generic_mono)] extern C { fn log_oneT(x: T); fn log_twoT(a: u32, b: T) - T; }块标志只在可行之处生效逐单态化路径至少需要一个类型参数因此块中的非泛型导入保持原样通过普通单 shim 绑定。一个块可以自由混合两者#[wasm_bindgen(experimental_generic_mono)] extern C { fn log_genericT(x: T); // per-monomorphisation fn log_u32(x: u32); // ordinary import, unaffected }直接在非泛型函数上写experimental_generic_mono仍是错误因为你要求了一件无法做到的事。参考测试 crates/cli/tests/reference/generic-import.rs 用#[wasm_bindgen(experimental_generic_mono)]块验证了继承block_inheritedT、block_inherited_twoT走 per-mono 路径各得__wbindgen_generic_*shim而block_not_generic(x: u32)仍走普通__wbg_*shimblock_inherited_impl_trait证明裸impl Trait无具名类型参数也会被块标志检测为泛型。不支持的形态以下形态在编译期被拒绝诊断指向违规声明。它们各自通常在类型擦除路径上仍可用所以修复方法通常是去掉experimental_generic_mono对类型参数的引用可变引用mut T、mut VecT或任何 referent 提及类型参数的mut以及嵌套在其他类型内部的对类型参数的引用如OptionT。裸T是受支持的对具象类型的可变引用如mut [u16]、mut dyn FnMut(u32)与非泛型导入路径绑定方式完全相同——限制只针对对类型参数的引用。顶层回调的泛型拥有输入/返回类型按 回调参数 所述另行支持。借用或更高阶的泛型回调输入与返回FnMut(T)、fora FnMut(a T)。返回引用。裸类型参数或对它的引用T作为variadic参数因为它可能单态化为标量而标量不可展开spread。catch导入错误位置中的类型参数ResultT, E且E泛型只有Ok类型被单态化错误类型永远是JsValue。slice_to_array用于元素类型提及类型参数的切片[T]、[VecT]、Option[T]。VectorRefIntoWasmAbi按具象 ABI 形态实现因此调用方写的任何约束都无法让任意T满足它元素类型必须具象。参见slice_to_array。reexport每次单态化都会制造一个绑定它没有明确定义的目标。assert_no_shim它断言不为导入生成 shim 函数。逐单态化代码生成在构造上就为每次实例化制造一个 shim永远无法满足该断言因此该组合被拒绝而非静默忽略。模式不是普通名字或_的参数例如元组模式fn fT((a, b): (u32, u32), x: T)报unsupported pattern in experimental_generic_mono imported function。生成的逐单态化 shim 必须按名转发每个参数因此它需要一个可命名的绑定。请给参数一个单一标识符。const 泛型参数也被拒绝但不是被experimental_generic_mono拒绝的wasm-bindgen 在任何泛型导入无论擦除与否上都不支持它们报unsupported in wasm-bindgen generics。去掉experimental_generic_mono也无济于事。类型参数默认值fn fT JsValue(x: T)报defaults for generic parameters are not allowed here——与 rustc 对任意普通函数默认参数给出的诊断相同。在擦除路径上默认值有意义因为它挑选了唯一共享绑定为之生成的具象类型而在experimental_generic_mono下没有单一的实例化可供挑选因为每次实例化都有自己的 shim。该错误由 wasm-bindgen 而非 rustc 报告仅仅是因为宏展开后原始签名没有幸存下来rustc 默认 deny 的invalid_type_param_defaultlint 永远看不到它。请去掉默认值若你希望它有含义请用擦除路径。上述拒绝的完整测试覆盖见 crates/macro/ui-tests/generic-per-mono-unsupported.rs无类型参数、mut T、OptionT、返回引用、variadic裸泛型/T/OptionT/BoxT/关联类型、catch泛型错误位置、T、元组(T, T)、数组[T; 2]、BoxT、元组模式参数、类型参数默认值、assert_no_shim、reexport、const 泛型、?Sized、泛型slice_to_array元素、借用/更高阶回调参数等对应.stderr文件固定诊断文本。冲突的导入colliding imports上述一切由宏在编译期报告。有一个失败由wasm-bindgenCLI更晚报告因为只有整个模块链接完成后才能检测到两个experimental_generic_mono导入在 shim 键的每个输入上都一致只在键不参与哈希的属性上不同。单态化只记录一个 shim 键它是以下内容的哈希Rust 函数名、js_name给定时、js_namespace、签名 token、module与任何cfg属性。它不覆盖catch、variadic、slice_to_array、structural/final以及 getter/setter 访问器种类。仅在这些之一上不同的两个导入会认领相同的键而 CLI 无法判断某个给定实例化指的是哪个绑定。与其静默绑定其中一个这会错误绑定另一个的每次单态化它报错并点名两个 JS 目标。修复方法让两者在 Rust 侧可区分——重命名其中一个 Rust 函数必要时加js_name保持 JS 可见名称不变或给它们不同的签名。关于T参数的说明裸T参数受支持且要求 referent 满足 wasm-bindgen 编组它所需的约束——即JsValue或 JS 句柄类型。传递SomeStruct普通 Rust 结构体或u32标量会被拒绝因为对它们没有TABI 表示请按值取值或传 JS 类型。参考测试 crates/cli/tests/reference/generic-import.rs 中log_refT(x: T)的调用log_ref(JsValue::from(fifteen))印证了这一点JsValue以 externref 编组JS 句柄 referent 按句柄索引编组log_generic_sliceT(xs: [T])走同样路线HRTBfora a [T]: IntoWasmAbi约束每个元素类型编组为自己的 typed-array 视图。同样方法路径上的attachT(this: Widget, other: T)实例化到Widget时Widget通过句柄的IntoWasmAbi for Widget实现编组。小结与选用建议experimental_generic_mono是 wasm-bindgen 泛型体系中的一个实验性出口当你要一个签名、多个 Rust 类型、关注具体编组方式时它为每个实例化生成独立 shim 与描述符当你要建模 JS 泛型容器、希望绑定数量恒定、不在乎具体编组时默认类型擦除guide/src/reference/working-with-generics.md仍是首选。它支持 trait 约束、impl Trait、生命周期、类级泛型与提升、回调参数、async可与绝大多数导入属性组合、可应用于整个块同时对mut T、返回引用、泛型variadic标量、catch泛型错误位置、泛型slice_to_array元素等形态在编译期明确拒绝并把键冲突留给 CLI 在链接后检测。需要深入验证或排查时可分别参考编译通过测试 crates/macro/pass-tests/generic-per-mono-class-generics.rs、拒绝测试 crates/macro/ui-tests/generic-per-mono-unsupported.rs、约束诊断测试 crates/macro/ui-tests/generic-per-mono-bounds.rs 与端到端参考 crates/cli/tests/reference/generic-import.rs。赞分享开发工具【免费下载链接】wasm-bindgenFacilitating high-level interactions between Wasm modules and JavaScript项目地址https://gitcode.com/gh_mirrors/wa/wasm-bindgen点击查看免费下载相关推荐ComfyUI-Manager终极指南高效管理自定义节点的完整解决方案ComfyUI Manager终极指南高效管理自定义节点的完整解决方案 ComfyUI Manager是ComfyUI生态系统中不可或缺的节点管理扩展专为A开发工具wasm-bindgen 官方示例全览基于 wasm-bindgen、js-sys 与 web-sys 的实战入门指南wasm bindgen 官方示例全览基于 wasm bindgen 、 js sys 与 web sys 的实战入门指南 wasm bindgen 仓库的开发工具wasm-bindgen 泛型类型全解析基于 js-sys 的类型化 JavaScript 互操作指南wasm bindgen 泛型类型全解析基于 js sys 的类型化 JavaScript 互操作指南 导读 本文以 wasm bindgen 官方指南中的开发工具上一篇Chrome二维码插件完整使用指南下一篇Tickeys让键盘打字变成一场听觉盛宴的macOS神器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表