Sway 语言结构化数据详解:Struct、Tuple 与 Enum 的类型系统、内存布局与实战用法
Sway 语言结构化数据详解Struct、Tuple 与 Enum 的类型系统、内存布局与实战用法【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway本文以 Sway 官方文档 docs/book/src/basics/structs_tuples_and_enums.md 为核心骨架结合仓库中examples/structs、examples/tuples、examples/enums三组可编译的 Sway 示例源码系统讲解 Sway 中三大复合类型结构体Struct又称积类型 Product Type、元组Tuple与枚举Enum又称和类型 Sum Type。读完本文你将掌握字段级访问控制、三种结构体实例化语法、解构语法、枚举携带数据的变体写法、嵌套枚举的正确姿势以及它们各自的运行时内存布局与开销从而能够写出类型安全、内存可预期、易于维护的智能合约代码。本文所有代码均来自仓库中真实可编译的示例工程读者可在examples/structs/、examples/tuples/、examples/enums/目录下通过forc build直接编译验证。1. 结构体Struct命名的类型分组Sway 中的结构体是一组类型的命名集合在类型理论中也被称为积类型product types。与大多数拥有结构体的语言类似Sway 并未在结构体上引入特殊机制——如果你熟悉面向对象编程可以把结构体理解为对象的数据属性部分。从源码层面看一个典型的结构体声明如下来自 examples/structs/src/data_structures.sw// the _data_structures_ module library; // Declare a struct type pub struct Foo { pub bar: u64, pub baz: bool, }1.1 字段级访问控制public 与 private结构体的数据属性被称为字段fields每个字段都可以是 public 或 private私有字段只能在声明该结构体的模块内部被访问公有字段可以在结构体可访问的任何地方被访问。这种字段级别的访问控制提供了比类型级别更细粒度的数据封装能力。在 data_structures.sw 中可以看到两类结构体的对比// 所有字段均为公有可在其他模块中直接实例化 pub struct Point { pub x: u64, pub y: u64, } pub struct Line { pub p1: Point, pub p2: Point, } pub struct TupleInStruct { pub nested_tuple: (u64, (u32, (bool, str))), } // 包含私有字段只能在本模块内实例化 pub struct StructWithPrivateFields { pub public_field: u64, private_field: u64, other_private_field: u64, }注意StructWithPrivateFields虽然自身是pub的但因其含有私有字段无法在_data_structures_模块之外通过结构体字面量直接创建实例。若要支持外部构造该结构体必须提供构造函数关联函数——即返回类型为该类型自身的关联函数。这符合 Sway 官方文档在 methods_and_associated_functions.md 中的明确说明公有但含私有字段的结构体若缺少公有构造函数就无法在声明模块之外被实例化。1.2 结构体实例化语法实例化结构体使用结构体实例化语法struct instantiation syntax它与声明语法非常相似区别在于把类型替换为表达式。Sway 支持三种实例化方式见 examples/structs/src/main.sw方式一直接硬编码字段值fn hardcoded_instantiation() - Foo { let mut foo Foo { bar: 42, baz: false, }; // 通过点语法访问并写入字段 foo.baz true; foo }方式二传入与字段名不同的变量fn variable_instantiation() - Foo { let number 42; let truthness false; let mut foo Foo { bar: number, baz: truthness, }; foo.baz true; foo }方式三字段名与变量名相同的简写shorthandfn shorthand_instantiation() - Foo { let bar 42; let baz false; let mut foo Foo { bar, baz }; foo.baz true; foo }官方文档给出的建议与注意事项Note三种实例化方式可以在同一次实例化中任意混用。 同时实例化时字段的书写顺序不影响正确性但官方推荐按字母顺序声明字段、并按相同字母顺序实例化。1.3 结构体解构Destructuring除了点语法逐字段访问Sway 还支持一次性从结构体中解构出多个变量。下面这段代码同样来自 main.sw展示了从简单结构体到嵌套结构体、再到结构体里嵌元组、元组里嵌结构体的完整解构能力fn struct_destructuring() { let point1 Point { x: 0, y: 0 }; // 将结构体字段解构到同名变量 let Point { x, y } point1; let point2 Point { x: 1, y: 1 }; // 不关心的字段可以用 .. 省略 let Point { x, .. } point2; let line Line { p1: point1, p2: point2, }; // 嵌套结构体的解构逐层展开 let Line { p1: Point { x: x0, y: y0 }, p2: Point { x: x1, y: y1 }, } line; // 结构体中嵌套的元组也可以解构 let tuple_in_struct TupleInStruct { nested_tuple: (42u64, (42u32, (true, ok))), }; let TupleInStruct { nested_tuple: (a, (b, (c, d))), } tuple_in_struct; // 元组中嵌套的结构体同样可以解构 let struct_in_tuple (Point { x: 2, y: 4 }, Point { x: 3, y: 6 }); let (Point { x: x0, y: y0 }, Point { x: x1, y: y1 }) struct_in_tuple; }值得留意的是解构语法与字段访问一样遵循模式匹配的直觉嵌套层次可以一层层展开未用到的字段用..忽略未用到的元组元素用_忽略元组部分详见第 2 节。1.4 结构体内存布局Struct Memory LayoutNote如果你是语言或编程新手本节信息并非必须掌握。Sway 结构体具有零内存开销在内存中每个字段按声明顺序依次排列运行时不会保存关于结构体名称或其他属性的任何元数据。换句话说结构体是纯编译期构造这一点与 Rust 一致而与带有运行时如 Java的语言不同。这意味着开发者可以直接用各字段类型大小的加和来估算一个结构体占用的存储空间非常适合在链上合约中做存储成本预算。2. 元组Tuple静态长度的混合类型聚合元组是一种基本的静态长度类型。根据 built_in_types.md 的定义元组是零个或多个类型的通用静态长度聚合元组内部组成类型以及元组的**元数arity**共同定义了元组的类型。元组内既可以放基本类型也可以放结构体和枚举。元组的完整实战示例见 examples/tuples/src/main.swlibrary; fn tuple() { // 方式一显式声明每个元素的类型 let tuple1: (u8, bool, u64) (100, false, 10000); // 方式二完全由编译器推断类型 let mut tuple2 (5, true, (Sway, 8)); // 通过点索引语法访问元组元素从 0 开始 let number tuple1.0; let sway tuple2.2.1; // 嵌套访问第 2 个元素的第 1 个元素 // 通过解构一次性取出多个元素 let (n1, truthness, n2) tuple1; // 不关心的元素用 _ 忽略 let (_, truthness, _) tuple2; // 元组本身可以可变可内部修改某个元素 tuple2.1 false; // 也可以一次性整体重新赋值必须保持相同的元素类型 tuple2 (9, false, (Fuel, 99)); }要点总结索引访问使用.语法从0开始计数。例如tuple1.0取出第一个u8tuple2.2.1先取第 2 个元素(Sway, 8)再取其中的第 1 个元素Sway。官方文档 built_in_types.md 同样验证了这一点x.1表示元组的第一个零索引即第二个值。解构访问let (n1, truthness, n2) tuple1;将元素一一绑定到新变量不需要的值用_占位。可变性声明为mut的元组可以逐个修改元素也可以整体重新赋值但新值的每个元素类型必须与原类型一致。与结构体的嵌套如 1.3 节所示元组和结构体可以互相嵌套且解构可以穿透所有嵌套层次。3. 枚举Enum一个类型多种变体枚举enumerations简称 enums类型理论中称为和类型sum types是一种可以是若干变体variant之一的类型。声明枚举即枚举其全部可能的变体。最简单的枚举——每个变体不带额外数据此时每个变体类型为()unit 类型// 来自 examples/enums/src/basic_enum.sw library; // Declare the enum enum Color { Blue: (), Green: (), Red: (), Silver: (), Grey: (), } fn main() { // 实例化语法为 枚举名::变体名 let blue Color::Blue; let silver Color::Silver; }实例化枚举变体的语法是EnumName::VariantName例如Color::Blue。3.1 携带数据的变体枚举的 StructEnums of Structs枚举变体可以携带额外数据。下面这个例子将结构体声明与枚举变体组合在一起来自 examples/enums/src/enum_of_structs.swlibrary; struct Item { price: u64, amount: u64, id: u64, } enum MyEnum { Item: Item, } fn main() { let my_enum MyEnum::Item(Item { price: 5, amount: 2, id: 42, }); }这里MyEnum::Item变体携带一个完整的Item结构体实例化时使用MyEnum::Item(Item { ... })的嵌套语法。这意味着枚举变体可以承载任意复杂的复合数据而不是仅限于基本类型。3.2 枚举的枚举嵌套枚举Enums of EnumsSway 还允许定义枚举的枚举即一个枚举变体的类型是另一个枚举。典型场景是错误类型聚合来自 examples/enums/src/enum_of_enums.swlibrary; pub enum Error { StateError: StateError, UserError: UserError, } pub enum StateError { Void: (), Pending: (), Completed: (), } pub enum UserError { InsufficientPermissions: (), Unauthorized: (), }3.2.1 推荐用法Preferred usage官方推荐直接使用各个独立的而非嵌套的枚举因为代码易读且行文简短。参考 examples/enums/src/enums_preferred.swlibrary; use ::enum_of_enums::{StateError, UserError}; fn preferred() { let error1 StateError::Void; let error2 UserError::Unauthorized; }3.2.2 不建议的嵌套用法Inadvisable如果坚持使用嵌套形式需要通过Error枚举层层包裹来实例化来自 examples/enums/src/enums_avoid.swlibrary; use ::enum_of_enums::{Error, StateError, UserError}; fn avoid() { let error1 Error::StateError(StateError::Void); let error2 Error::UserError(UserError::Unauthorized); }官方文档特别列出了这种写法的关键问题必须把所有涉及的枚举全部导入而不只是导入Error这一个枚举代码行可能变得不必要地冗长取决于命名语法并非最符合人体工学的写法。因此除非有强聚合需求否则应优先选择独立枚举。3.3 枚举内存布局Enum Memory LayoutNote如果你是语言或编程新手本节信息并非必须掌握。与零开销的结构体不同枚举存在内存开销。为了在运行时知道当前表示的是哪一个变体Sway 会为枚举存储一个一字的8 字节tag标签tag 之后预留的空间大小等于最大变体的大小。因此枚举的内存大小计算公式为枚举大小 8 字节tag 最大变体的大小以最上面的Color为例所有变体都是()大小为 0 字节最大变体大小为 0因此Color在内存中占8 字节。若某个枚举含有一个u25632 字节变体则该枚举整体占 8 32 40 字节。这一设计保证任何变体都能放入预留空间中代价是按最大变体预留而可能造成内部填充浪费——在设计链上存储结构时值得权衡。4. 深入源码从示例工程到编译验证上述所有示例均以独立的forc工程存在于仓库中可直接编译验证工程目录核心源码工程清单examples/structsdata_structures.sw、main.swForc.tomlexamples/tuplesmain.swForc.tomlexamples/enumsbasic_enum.sw、enum_of_structs.sw、enum_of_enums.sw、enums_preferred.sw、enums_avoid.swForc.toml从 examples/structs/Forc.toml 可以看到这些示例工程的implicit-std false即不隐式引入标准库属于聚焦语言核心语法的精简演示工程便于读者单独研读类型系统本身。值得注意的工程组织方式是library;声明与模块导入的结合data_structures.sw 作为一个库模块暴露Foo、Point、Line、TupleInStruct等类型main.sw 通过mod data_structures;与use data_structures::{Foo, Line, Point, TupleInStruct};导入后使用——这正是前文私有字段只能在声明模块内访问的模块边界实例包含私有字段的StructWithPrivateFields没有被导入使用印证了封装约束在实际编译中的存在。5. 实战决策建议综合官方文档与源码示例在实际 Sway 合约开发中可遵循以下原则选择正确的复合类型需要带名字的多个字段时用结构体只需要轻量、位置化、静态长度的混合数据时用元组需要有限几种可能状态时用枚举。封装优先优先把字段设为私有对外只暴露必要的公有字段或构造函数借助字段级访问控制实现细粒度封装对应 methods_and_associated_functions.md 中关于构造函数的设计建议。内存可预期结构体零开销、大小等于各字段之和枚举固定为 8 字节 tag 加上最大变体大小。在链上存储storage设计中据此估算存储成本并避免超大变体造成的空间浪费。枚举保持扁平优先使用独立枚举而非嵌套枚举保证可读性与可维护性仅在确实需要统一错误聚合时再考虑嵌套形式。解构提升可读性对结构体与元组善用解构与..、_占位符减少逐字段取值的样板代码。以上内容严格对应 Sway 官方文档 structs_tuples_and_enums.md 的完整章节结构并辅以 examples/structs、examples/tuples、examples/enums 中的真实源码与 built_in_types.md 的类型定义可作为学习与查阅 Sway 复合类型的第一手资料。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考