ARTICLE DETAIL

资讯详情

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

fuels-rs 自定义类型详解:Struct / Enum 的 ABI 绑定生成与泛型支持

fuels-rs 自定义类型详解:Struct / Enum 的 ABI 绑定生成与泛型支持 fuels-rs 自定义类型详解Struct / Enum 的 ABI 绑定生成与泛型支持【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs本文聚焦于 Fuel Rust SDKfuels-rs中「自定义类型」的端到端链路当你在 Sway 合约中声明了结构体struct或枚举enumabigen!宏会自动生成等价的 Rust 类型让你在 Rust 侧以原生类型系统直接构造参数、接收返回值甚至享受与 Rust 一致的泛型编程体验。读完本文你将掌握如何让 Sway 自定义类型无缝搬运到 Rust、如何让泛型 struct/enum 穿过 ABI 边界往返传递以及当 Sway 允许声明了却没用的泛型参数时fuels-rs 如何借助PhantomData与代码生成优雅兜底。自定义类型与abigen!从 Sway 到 Rust 的一键映射abigen!是 fuels-rs 提供的声明式过程宏输入格式为ProgramType(nameMyProgramType, abimy_program-abi.json)...其中ProgramType可取Contract、Script或Predicatename是生成绑定的名称abi指向 JSON ABI 文件的路径或其内容。abigen!读取的 ABI 中携带了完整的类型声明信息因此你在 Sway 代码中定义的结构体与枚举都会被 SDK 的abigen!宏自动生成出等价的 Rust 类型。也就是说合约一旦编译出-abi.jsonRust 侧无需手写任何镜像类型也不用手工编写编解码逻辑——类型定义由abigen!在编译期生成并自动附带编码、解码所需的全部 trait 实现。在生成的代码中每个ProgramType拥有独立的mod若多个程序共享同一类型定义abigen!会把该类型提升到额外的shared_types模块只生成一次再通过pub use在每个mod中重新导出保证访问路径不感知共享详见 the-abigen-macro.md。官方还强烈建议在同一个abigen!调用里生成全部绑定既能触发类型共享又能规避多次调用产生的命名冲突。从 Sway 结构体到 Rust 类型CounterConfig实战假设你的 Sway 合约中声明了这样一个结构体struct CounterConfig { dummy: bool, initial_value: u64, }经过abigen!之后CounterConfig会真正出现在你的 Rust 文件作用域里。仓库端到端测试 e2e/tests/types_contracts.rs 展示了完整过程// Generates the bindings from an ABI definition inline. // The generated bindings can be accessed through MyContract. abigen!(Contract( name MyContract, abi e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json )); // Here we can use CounterConfig, a struct originally // defined in the contract. let counter_config CounterConfig { dummy: true, initial_value: 42, };注意 Sway 字段initial_value在生成的 Rust 类型中以同名字段直接出现结构体构造语法与原生 Rust 完全一致。随后该类型可直接作为合约方法的入参let response contract_methods .initialize_counter(counter_config) .call() .await?; assert_eq!(42, response.value);你可以在该作用域内自由使用这些自定义类型struct 或 enum——既能作为实参传给函数也能接收函数调用返回的自定义类型。同一测试里increment_counter返回值与链上状态校验assert_eq!(52, response.value)证明了这种双向流通完整成立。把自定义类型嵌入复杂结构自定义类型并不局限于孤零零传参仓库测试覆盖大量嵌套场景嵌套结构体nested_structs测试构造AllStruct { some_struct: SomeStruct { field, field_2 } }并整体往返结构体数组 / 枚举数组array_of_structs与array_of_enums把一个Person数组或State枚举数组整体传入合约结构体内的枚举enum_inside_struct测试中的Cocktail { the_thing_you_mix_in: Shaker::Mojito(11), glass: 333 }把枚举嵌进结构体字段结构体内的结构体 / 枚举内的枚举type_inside_enum测试覆盖Recipe结构体、数组、字符串出现在枚举变体里以及EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)))的多层嵌套枚举。这些用例共同说明只要底层类型在 Sway 侧合法组合生成的 Rust 绑定就保持同样形状开发者可以放心按 Rust 直觉组织数据。泛型SimpleGeneric从 Sway 到 Rust 的旅程fuels-rs同时支持泛型结构体与泛型枚举。如果你熟悉 Rust这本质上就是你熟悉的struct MyStructT泛型支持。对应 Sway 合约见 e2e/sway/types/contracts/generics/src/main.swstruct SimpleGenericT { single_generic_param: T, } abi MyContract { fn struct_w_generic(arg1: SimpleGenericu64) - SimpleGenericu64; } impl MyContract for Contract { fn struct_w_generic(arg1: SimpleGenericu64) - SimpleGenericu64 { let expected SimpleGeneric { single_generic_param: 123u64, }; assert(arg1.single_generic_param expected.single_generic_param); expected } }Rust 侧见 e2e/tests/types_contracts.rs的泛型实例化方式与原生 Rust 无异// simple struct with a single generic param let arg1 SimpleGeneric { single_generic_param: 123u64, }; let result contract_methods .struct_w_generic(arg1.clone()) .call() .await? .value; assert_eq!(result, arg1);泛型的表现力远不止单参数结构体同一generics_test中还验证了泛型内部委托PassTheGenericOnK { one: SimpleGenericK }把类型参数转发给另一个泛型结构体数组里的泛型StructWArrayGenericL { a: [L; 2] }元组里的泛型StructWTupleGenericM { a: (M, M) }枚举变体承载泛型EnumWGenericN { A: u64, B: N }复合压测MegaExampleT, U把数组、元组、Vec、嵌套枚举糅合进泛型参数验证编码在极端形状下依然正确。其原理在于泛型在实例化后会被实体化为具体类型参与 ABI 编解码因此合约接受SimpleGenericu64时SDK 生成代码就按u64完成编解码。未被使用的泛型参数Sway 与 Rust 的差异Sway 声明 struct / enum 时允许存在完全未被字段引用的泛型类型参数struct SomeStructT, K { field: u64 } enum SomeEnumT, K { One: u64 }同样的写法放到 Rust 中编译器会提示T、K必须被使用或移除。为了让生成的 Rust 绑定合法fuels-rs 借助标准库的PhantomData类型占位这些参数。上面例子的生成绑定大致如下struct SomeStructT, K { pub field: u64, pub _unused_generic_0: PhantomDataT, pub _unused_generic_1: PhantomDataK, } enum SomeEnumT, K { One(u64), IgnoreMe(PhantomDataT, PhantomDataK) }该行为实现在 packages/fuels-code-gen/src/program_bindings/utils.rsgenerate_parameters_for_unused_generics对比「声明的泛型列表」与「字段类型中实际引用的命名泛型」对差额逐一生成_unused_generic_{index}字段名与::core::marker::PhantomData#generic类型generate_variant_for_unused_generics为枚举生成携带全部PhantomData的附加变体IgnoreMe同样标记#[Ignore]。两个细节值得注意#[Ignore]是 SDK 内部约定告知编解码推导这些字段/变体不参与 ABI 编解码——它们只是类型层面的占位符不会污染链上编码格式生成的泛型声明会被加上 trait 边界T: ::fuels::core::traits::Tokenizable ::fuels::core::traits::Parameterize见 utils.rs确保任意泛型实参都可编码为 Token并能通过Parameterize描述 ABI 形状。new()构造器绕开PhantomData的开发体验优化为减轻PhantomData对开发体验的影响生成的结构体会额外提供new方法让你构造实例时完全不必关心占位字段assert_eq!( StructUnusedGenericu16, u32::new(15), StructUnusedGeneric { field: 15, _unused_generic_0: std::marker::PhantomData, _unused_generic_1: std::marker::PhantomData } );该代码取自 examples/types/src/lib.rs其中StructUnusedGenericu16, u32::new(15)等价于手动填充两个PhantomData字段的完整构造——new内部以PhantomData::default()填充占位字段。生成逻辑见 custom_types/structs.rsnew只接受业务字段。另外如果结构体没有任何字段SDK 会额外派生Default见 custom_types/structs.rs让你可以用EmptyStruct {}字面量自由出入参、返回与比较——对应测试calls_with_empty_struct正是围绕这一点展开。枚举里的IgnoreMe变体match 时记得兜底枚举的处理策略与结构体不同所有PhantomData被收进名为IgnoreMe的附加变体因此对这类枚举做穷尽匹配时必须显式处理它let my_enum EnumUnusedGenericu32, u64::One(15); match my_enum { EnumUnusedGeneric::One(_value) {} EnumUnusedGeneric::IgnoreMe(..) panic!(Will never receive this variant), }这段代码出自 examples/types/src/lib.rsIgnoreMe内只有零大小的PhantomDataFuelVM 永远不会解码出该变体实际运行中走不到panic!分支——它只是让 Rust 类型系统与穷尽性检查闭嘴。生成侧实现见 custom_types/enums.rs枚举同样派生Clone、Debug、Eq、PartialEq、Parameterize、Tokenizable、TryFrom并把IgnoreMe追加在用户声明的变体之后。生成的绑定还包含什么derive 全景与 trait 支撑从 custom_types/structs.rs 与 custom_types/enums.rs 可见自定义类型一经生成即自动具备完整能力栈能力来源用途Clone/Debug/Eq/PartialEqderiveRust 侧可复制、可比较、可打印调试Defaultderive仅空字段结构体空结构体的默认构造Parameterizefuels::macros::Parameterize描述类型 ABI 形状供编码器分配空间Tokenizablefuels::macros::Tokenizable类型实例与 ABI Token 互转TryFromfuels::macros::TryFrom允许Vecu8字节流try_into()反序列化为该类型new手写 impl 块忽略PhantomData占位字段的便捷构造器Log视场景类型出现在日志上下文时提供LOG_ID相关日志编解码其中Parameterize、Tokenizable、TryFrom三个派生宏定义位于 packages/fuels-macros/src/derive/。正因为这些 trait 自动实现来自链上的原始字节才能经 ABI 解码器反推出带完整语义的CounterConfig、SimpleGenericu64而 Rust 侧构造的类型也会在调用前被 ABI 编码器正确压平为 FuelVM 期望的编码。常见陷阱与规避结合前文与仓库测试可归纳出几个高频注意点找不到生成的类型若rustc找不到某个类型多半是多个abigen!生成同名类型、pub use被冲突跳过。此时应使用全限定路径如abigen_bindings::whatever_contract_mod::TheType。务必穷尽IgnoreMe只要原始枚举声明过未使用的泛型参数生成的 Rust 枚举就多出IgnoreMe变体任何match都要补充EnumUnusedGeneric::IgnoreMe(..) ...分支。优先用new而非手填PhantomData构造带未使用泛型参数的结构体时走T::new(...)测试代码e2e/tests/types_contracts.rs中的StructUnusedGeneric::new(15)、EnumUnusedGeneric::One(15)即标准用法。字段命名规整代码生成会把字段名按snake_case规整并规避 Rust 保留关键字冲突utils.rs即使 Sway 字段名碰巧是if、let等也安全。上述内容的可运行验证集中于 e2e/tests/types_contracts.rs 的generics_test、call_with_structs等测试Sway 侧契约可对照 e2e/sway/types/contracts/generics/src/main.sw 阅读从而形成Sway 定义 → abigen 生成 → Rust 调用的完整闭环。【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表