Appearance
合约 contract
在 Tact 中,合约类似于流行的面向对象语言中的类,除了它们的实例部署在区块链上,并且不能像结构体(Structs)和消息(Messages)那样传递。
自引用
合约和特征(traits)有一个内置的标识符 self
,用于引用它们的字段(持久状态变量和常量)和方法(内部函数):
solidity
contract Example {
// 持久状态变量
foo: Int;
init() {
self.foo = 42; // <- 通过 self 引用变量 foo。
}
}
结构
每个合约可以包含:
- 持久状态变量
- 构造函数
init()
- 合约常量
- 获取器函数
- 接收器函数
- 内部函数
此外,合约可以继承来自特征的所有声明,并覆盖一些默认行为。
持久状态变量
合约可以定义在合约调用之间持久存在的状态变量。TON 中的合约需要按照它们消耗的持久空间比例支付租金,因此鼓励通过序列化使用紧凑的表示形式。
solidity
contract Example {
// 持久状态变量
val: Int; // Int
val32: Int as uint32; // 序列化为 32 位无符号的 Int
mapVal: map<Int, Int>; // Int 键对 Int 值
optVal: Int?; // Int 或 null
}
状态变量必须在 init()
函数中有默认值或初始化,该函数在合约部署时运行。唯一的例外是类型为 map<k, v>
的持久状态变量,因为它们默认初始化为空。
合约常量
与变量不同,常量不能改变。它们的值在编译时计算,并且在执行期间不能改变。
合约内部和外部定义的常量之间没有太大区别。在项目中的其他合约可以使用外部定义的常量。
常量初始化必须相对简单,只能依赖于编译期间已知的值。例如,如果你加两个数字,编译器将在构建期间计算结果并将结果放入你的编译代码中。
你可以在接收器和获取器中读取常量。
与合约变量不同,合约常量不消耗持久状态空间。它们的值直接存储在合约的代码单元(Cell)中。
solidity
// 全局常量在编译时计算,不能改变
const GlobalConst1: Int = 1000 + ton("42") + pow(10, 9);
contract Example {
// 合约常量也在编译时计算,不能改变
const ContractConst1: Int = 2000 + ton("43") + pow(10, 9);
// 合约常量可以是枚举的简单替代品
const StateUnpaid: Int = 0;
const StatePaid: Int = 1;
const StateDelivered: Int = 2;
const StateDisputed: Int = 3;
// 初始化常量不需要 init 函数
init() {}
get fun sum(): Int {
// 从任何地方访问常量
return GlobalConst1 + self.ContractConst1 + self.StatePaid;
}
}
构造函数 init()
在合约部署时运行构造函数 init()
。
如果合约有任何没有指定默认值的持久状态变量,它必须在此函数中初始化它们。
solidity
contract Example {
// 持久状态变量
var1: Int = 0; // 使用默认值 0 初始化
var2: Int; // 必须在 init() 函数中初始化
// 构造函数
init() {
self.var2 = 42;
}
}
要在内部函数、接收器或获取器中获取目标合约的初始状态,请使用 initOf
表达式。
获取器函数
获取器函数无法从其他合约访问,只导出到链下世界。
此外,获取器不能修改合约的状态变量,只能读取它们的值并在表达式中使用它们。
solidity
contract HelloWorld {
foo: Int;
init() {
self.foo = 0;
}
// 带返回类型 Int 的获取器函数
get fun foo(): Int {
return self.foo; // 这里不能改变 self.foo
}
}
接收器函数
Tact 中的接收器函数可以是以下三种之一:
receive()
,接收来自其他合约的内部消息。bounced()
,当这个合约发出的消息反弹回来时被调用。external()
,没有发送者,任何人都可以发送的消息。
solidity
message CanBounce {
counter: Int;
}
contract HelloWorld {
counter: Int;
init() {
self.counter = 0;
}
get fun counter(): Int {
return self.counter;
}
// 内部消息接收器,响应字符串消息 "increment"
receive("increment") {
self.counter += 1;
// 将消息发送回发送者
send(SendParameters{
to: sender(),
value: 0,
mode: SendRemainingValue | SendIgnoreErrors,
body: CanBounce{counter: self.counter}.toCell()
});
}
// 反弹消息接收器,当消息反弹回此合约时调用
bounced(src: bounced<MsBounced>) {
self.counter = 0; // 如果消息反弹,则重置计数器
}
// 外部消息接收器,响应链下消息 "hello, it's me"
external("hello, it's me") {
// 因为没有发送者,所以无法回复!
self.counter = 0;
}
}
内部函数
这些函数的行为类似于流行的面向对象语言中的私有方法——它们是合约内部的,可以通过前缀 self
来调用。这就是为什么有时内部函数也被称为“合约方法”。
内部函数可以访问合约的持久状态变量和常量。
它们只能从接收器、获取器和其他内部函数调用,但不能从其他合约或 init()
调用。
solidity
contract Functions {
val: Int = 0;
init() {}
// 这个合约方法只能从这个合约内部调用并访问其变量
fun onlyZeros() {
require(self.val == 0, "Only zeros are permitted!");
}
// 接收器函数,调用内部函数 onlyZeros
receive("only zeros") {
self.onlyZeros();
}
}
请注意,Tact 还支持其他类型的函数,详见:函数。