结构(Structs)和消息(Messages)
Tact 支持许多专为智能合约使用而定制的 原始数据类型。 不过,使用单独的存储方式往往会变得繁琐,因此有 Structs 和 Messages可以将类型组合在一起。 不过,使用单独的存储方式往往会变得繁琐,因此有 Structs 和 Messages可以将类型组合在一起。
结构
结构体可以定义包含多个不同类型字段的复杂数据类型。 它们还可以嵌套。 它们还可以嵌套。
struct Point { x: Int as int64; y: Int as int64;}
struct Line { start: Point; end: Point;}
结构体还可以包含默认字段,并定义可选类型的字段。 结构体还可以包含默认字段和定义[可选类型]字段(/book/optionals)。 如果您有很多字段,但又不想一直在 new instances 中为它们指定通用值,那么这将非常有用。
struct Params { name: String = "Satoshi"; // default value
age: Int?; // field with an optional type Int? // and default value of null
point: Point; // nested Structs}
结构体还可用作获取器(getters)或其他内部函数的返回值。 它们有效地允许单个获取器返回多个返回值。
contract StructsShowcase { params: Params; // Struct as a contract's persistent state variable
init() { self.params = Params{ point: Point{ x: 4, y: 2, }, }; }
get fun params(): Params { return self.params; }}
请注意,结构声明中的最后一个分号 ;
是可选项,可以省略:
struct Mad { ness: Bool }
struct MoviesToWatch { wolverine: String; redFunnyGuy: String}
字段的顺序很重要,因为它与TL-B 模式 中的内存布局一致。 不过,与某些采用手动内存管理的语言不同,Tact 在字段之间没有任何填充。
消息
消息中可以包含 结构体:
struct Point { x: Int; y: Int;}
message Add { point: Point; // holds a struct Point}
消息与 结构体几乎相同,唯一不同的是,消息在序列化时有一个 32 位整数头,包含唯一的数字 id,通常称为 opcode(操作码)。 这使得消息可以与 接收者(receivers) 一起使用,因为合约可以根据这个 id 区分不同类型的消息。
Tact 会为每个接收到的消息自动生成这些唯一 ID(操作码),但也可以手动覆盖:
// This Message overwrites its unique id with 0x7362d09cmessage(0x7362d09c) TokenNotification { forwardPayload: Slice as remaining;}
这对于要处理特定智能合约的某些操作码(如 Jetton standard)的情况非常有用。 该合约能够处理的操作码简表为此处以 FunC 表示。 它们是智能合约的接口。
操作
实例化
创建 Struct 和 Message 实例类似于 function calls,但需要用大括号 {}
(大括号)代替小括号 ()
指定参数:
struct StA { field1: Int; field2: Int;}
message MsgB { field1: String; field2: String;}
fun example() { // Instance of a Struct StA StA{ field1: 42, field2: 68 + 1, // trailing comma is allowed };
// Instance of a Message MsgB MsgB{ field1: "May the 4th", field2: "be with you!", // trailing comma is allowed };}
当分配给字段的变量或常量的名称与该字段的名称相同时,Tact 提供了一种方便的语法捷径,有时称为字段戏法。 有了它,你就不必输入多余的内容:
struct PopQuiz { vogonsCount: Int; nicestNumber: Int;}
fun example() { // Let's introduce a couple of variables let vogonsCount: Int = 42; let nicestNumber: Int = 68 + 1;
// You may instantiate the Struct as usual and assign variables to fields, // but that is a bit repetitive and tedious at times PopQuiz{ vogonsCount: vogonsCount, nicestNumber: nicestNumber };
// Let's use field punning and type less, // because our variable names happen to be the same as field names PopQuiz{ vogonsCount, nicestNumber, // trailing comma is allowed here too! };}
转换为 Cell
, .toCell()
通过使用 .toCell()
扩展函数,可以将任意 Struct 或 Message 转换为 cell
类型:
struct Big { f1: Int; f2: Int; f3: Int; f4: Int; f5: Int; f6: Int;}
fun conversionFun() { dump(Big{ f1: 10000000000, f2: 10000000000, f3: 10000000000, f4: 10000000000, f5: 10000000000, f6: 10000000000, }.toCell()); // x{...cell with references...}}
从 Cell
或 Slice
获取,.fromCell()
和 .fromSlice()
无需通过一系列相关的 .loadSomething()
函数调用来手动解析 Cell
或 Slice
,而是可以使用 .fromCell()
和 .fromSlice()
扩展函数。这些扩展函数将所提供的 Cell
或 Slice
转换为所需的 Struct 或 Message。
这些扩展函数仅尝试根据 Struct 或 Message 的结构解析 Cell
或 Slice
。 如果布局不匹配,可能会抛出各种异常—确保用 try...catch
块封装代码,以防止意外结果。
struct Fizz { foo: Int }message(100) Buzz { bar: Int }
fun constructThenParse() { let fizzCell = Fizz{foo: 42}.toCell(); let buzzCell = Buzz{bar: 27}.toCell();
let parsedFizz: Fizz = Fizz.fromCell(fizzCell); let parsedBuzz: Buzz = Buzz.fromCell(buzzCell);}
转换法
只要通过 .toCell()
和 .fromCell()
函数在 cell
/slice
和 结构体/消息 之间进行转换,以下规律就会成立:
struct ArbitraryStruct {}message(0x2A) ArbitraryMessage {}
fun lawOne() { let structInst = ArbitraryStruct{}; let messageInst = ArbitraryMessage{};
ArbitraryStruct.fromCell(structInst.toCell()); // = structInst ArbitraryMessage.fromCell(messageInst.toCell()); // = messageInst
// Same goes for Slices, with .toCell().asSlice() and .fromSlice()
ArbitraryStruct.fromSlice(structInst.toCell().asSlice()); // = structInst ArbitraryMessage.fromSlice(messageInst.toCell().asSlice()); // = messageInst}
- 对于任何与给定 Struct/Message 具有相同 TL-B 布局的
cell
,调用Struct.fromCell()
(或Message.fromCell()
),然后通过.toCell()
将结果转换为Cell
,就会得到原始cell
的副本:
struct ArbitraryStruct { val: Int as uint32 }message(0x2A) ArbitraryMessage {}
fun lawTwo() { // Using 32 bits to store 42 just so this cellInst can be // re-used for working with both ArbitraryStruct and ArbitraryMessage let cellInst = beginCell().storeUint(42, 32).endCell();
ArbitraryStruct.fromCell(cellInst).toCell(); // = cellInst ArbitraryMessage.fromCell(cellInst).toCell(); // = cellInst
// Same goes for Slices, with .fromSlice() and .toCell().asSlice() let sliceInst = cellInst.asSlice();
ArbitraryStruct.fromSlice(sliceInst).toCell().asSlice(); // = sliceInst ArbitraryMessage.fromSlice(sliceInst).toCell().asSlice(); // = sliceInst}