跳转到内容

语句

以下语句可出现在 function 主体的任何位置。

let语句

let 语句允许声明局部变量和 block-scoped 变量。

在Tact中,声明一个局部变量总是需要一个初始值。 但是,也可以省略类型标注,Tact 会尝试从初始值推断类型:

let value: Int = 123; // full declaration with type and value
let vInferred = 123; // inferred type Int
let vExplicitCtx: Context = context(); // explicit type Context, a built-in Struct
let vCtx = context(); // inferred type Context

请注意,null的初始值既可以指具有任意 KV类型的空map<K, V>,也可以指故意不为optional类型设置任何其他值。 这就是为什么在声明 optionalmap<K, V>时,需要明确指定类型,因为无法推断:

let vOptional: Int? = null; // explicit type Int or null
let vOptInt = 42; // implicit type Int
vOptInt = null; // COMPILATION ERROR!
let vMap: map<Int, Int> = emptyMap(); // explicit type map<Int, Int>
let vMapWithSerialization: map<Int as uint8, Int as uint8> = emptyMap();

用下划线 _ 命名局部变量时,其值将被视为未使用并丢弃。 当你不需要某个函数的返回值(有副作用),并想明确地将变量标记为未使用时,这种方法就很有用。 注意,不能访问通配符变量名 _: 当你不需要某个函数的返回值(有副作用),并想明确地将变量标记为未使用时,这种方法就很有用。 注意,不能访问通配符变量名 _

let _ = someFunctionWithSideEffects(); // with type inference
let _: map<Int, Int> = emptyMap(); // with explicit type
dump(_); // COMPILATION ERROR! Cannot access _

return 语句

return 语句结束 function 的执行,并指定要返回给 function 调用者的值。

// Simple wrapper over stdlib function now()
fun getTimeFromNow(offset: Int): Int {
return now() + offset;
}

Block

块语句用于组合零个或多个语句。 块语句用于组合零个或多个语句。 块由一对大括号(“大括号”、{})分隔,包含一个由零个或多个语句和声明组成的列表。

某些语句,如 letreturn,必须以结束分号 ; 结束。 不过,语块中最后一条语句的分号是可选的,可以省略。

{ // <- start of the block
// arbitrary statements:
let value: Int = 2 + 2;
dump(value);
} // <- end of the block
{ dump(2 + 2) } // a block with only one statement,
// omitted the last and only semicolon
{
let nah = 3 * 3 * 3; // a block with two statements,
let yay = nah + 42 // but without the last semicolon
}

表达式

表达式语句是一种表达式,用于预期需要语句的地方。 表达式被求值后,其结果将被丢弃—因此,它只适用于有副作用的表达式,如执行函数或更新变量。

dump(2 + 2); // stdlib function

赋值

赋值语句使用 赋值运算符 (=)或 增强赋值运算符 (赋值与运算相结合):

let value: Int; // declaration
value = 5; // assignment
value += 5; // augmented assignment (one of the many, see below)

解构赋值

Available since Tact 1.6 (not released yet)

解构赋值是一种简洁的方法,可以将结构消息解包为不同的变量。 它镜像了实例化语法,但不是创建新的结构体消息,而是将每个字段或部分字段绑定到它们各自的变量。

该语法源于 let语句,它不是直接指定变量名,而是在赋值运算符 =的左侧指定结构类型,与右侧值的结构类型相对应。

// Definition of Example
struct Example { number: Int }
// An arbitrary helper function
fun get42(): Example { return Example { number: 42 } }
fun basic() {
// Basic syntax of destructuring assignment (to the left of "="):
let Example { number } = get42();
// ------- ------ -------
// ↑ ↑ ↑
// | | gives the Example Struct
// | definition of "number" variable, derived
// | from the field "number" in Example Struct
// target structure type "Example"
// to destructure fields from
// Same as above, but with an instantiation
// to showcase how destructuring syntax mirrors it:
let Example { number } = Example { number: 42 };
// ----------------------
// ↑
// instantiation of Example Struct
// Above examples of syntax are roughly equivalent
// to the following series of statements:
let example = Example { number: 42 };
let number = example.number;
}

就像在实例化中一样,允许使用尾随逗号。

struct Example { number: Int }
fun trailblazing() {
let Example {
number, // trailing comma inside variable list
} = Example {
number: 42, // trailing comma inside field list
};
}

要创建一个不同变量名下的绑定,请在分号:之后指定它。

// Similar definition, but this time field is called "field", not "number"
struct Example { field: Int }
fun naming(s: Example) {
let Example { field: varFromField } = s;
// ------------ ↑
// ↑ |
// | instance of Example Struct, received
// | as a parameter of the function "naming"
// definition of "varFromField" variable, derived
// from the field "field" in Example Struct
}

请注意,绑定的顺序无关紧要——所有字段都保留其名称下的值和类型,不管它们在各自的 StructMessage 中在定义中的顺序。

// "first" goes first, then goes "second"
struct Two { first: Int; second: String }
fun order(s: Two) {
let Two { second, first } = s;
// ------ -----
// ↑ ↑
// | this variable will be of type Int,
// | same as the "first" field on Struct Two
// this variable will be of type String,
// same as the "second" field in Struct Two
}

解构赋值是彻底的,并且需要将所有字段指定为变量。 要故意忽略某些字段,可以使用下划线 _,这将丢弃被考虑的字段的值。 注意,这些通配符变量名 _ 无法访问:

// "first" goes first, then goes "second"
struct Two { first: Int; second: String }
fun discard(s: Two) {
let Two { second: _, first } = s;
// ---
// ↑
// discards the "second" field, only taking the "first"
}

若要完全忽略其余字段,请在列表末尾使用 ..

struct Many { one: Int; two: Int; three: Int; fans: Int }
fun ignore(s: Many) {
let Many { fans, .. } = s;
// --
// ↑
// ignores all the unspecified fields,
// defining only "fans"
}

Branches

控制代码流

if...else

在执行 if...else 语句时,首先会对指定条件进行评估。 如果结果值为 true,则执行下面的语句块。 否则,如果条件评估结果为 false,将执行可选的 else 块。 如果缺少 else 块,则什么也不会发生,执行仍将继续。

常规 if 语句:

// condition
// ↓
if (true) { // consequence, when condition is true
dump(2 + 2);
}

else 块:

// condition
// ↓
if (2 + 2 == 4) {
// consequence, when condition is true
dump(true);
} else {
// alternative, when condition is false
dump(false);
}

使用嵌套的 if...else

// condition
// ↓
if (2 + 2 == 3) {
// consequence, when condition is true
dump("3?");
// condition2
// ↓
} else if (2 + 2 == 4) {
// another consequence, when condition2 is true
dump(true);
} else {
// alternative, when both condition and condition2 are false
dump(false);
}

try...catch

try...catch语句由一个 try块和一个可选的 catch块组成,它接收一个 Int退出码作为唯一参数。 首先执行 try 块中的代码,如果失败,则执行 catch 块中的代码,并尽可能回滚 try 块中的更改。

常规 try 语句:

fun braveAndTrue() {
// Lets try and do something erroneous
try {
nativeThrow(42); // throwing with exit code 42
}
// The following will be executed as the erroneous code above was wrapped in a try block
dump(42);
}

catch (e) 块:

fun niceCatch() {
// Lets try and do something erroneous
try {
nativeThrow(42); // throwing with exit code 42
} catch (err) {
dump(err); // this will dump the exit code caught, which is 42
}
}

使用嵌套的 try...catch

try {
// Preparing an x equal to 0, in such a way that Tact compiler won't realize it (yet!)
let xs: Slice = beginCell().storeUint(0, 1).endCell().beginParse();
let x: Int = xs.loadUint(1); // 0
try {
throw(101); // 1. throws with exit code 101
} catch (err) { // 2. catches the error and captures its exit code (101) as err
return err / x; // 3. divides err by x, which is zero, throwing with exit code 4
}
} catch (err) { // 4. catches the new error and captures its exit code (4) as err
// ^^^ this works without name collisions because the previous err
// has a different scope and is only visible inside the previous catch block
dump(err); // 5. dumps the last caught exit code (4)
}

请注意,与 let 语句类似,在 catch () 子句中捕获的退出码可以通过指定下划线 _ 来丢弃:

try {
throw(42);
} catch (_) {
dump("I don't know the exit code anymore");
}

循环

有条件地多次重复某些代码块。

repeat

repeat 循环执行一段代码指定的次数。 重复次数应以正 3232-bit Int的形式给出,范围从 11 到 2^31 - 1$。 如果数值更大,则会出现退出码 5,“整数超出预期范围 “的错误。

如果指定的重复次数等于 00 或包含范围 2256-2^{256}1-1 中的任何负数,则忽略该值,不执行代码块。

let twoPow: Int = 1;
// Repeat exactly 10 times
repeat (10) {
twoPow *= 2;
}
// Skipped
repeat (-1) {
twoPow *= 3333;
}
twoPow; // 1024

while

只要给定条件为 truewhile 循环就会继续执行代码块。

在下面的示例中,每次迭代时,x 的值都会递减 11,因此循环将运行 1010 次:

let x: Int = 10;
while (x > 0) {
x -= 1;
}

do...until

do...until循环是一个后测试循环,它至少执行一次代码块,然后继续执行,直到给定条件变为 true

在下面的示例中,每次迭代时,x 的值都会递减 11,因此循环将运行 1010 次:

let x: Int = 10;
do {
x -= 1; // executes this code block at least once
} until (x <= 0);

foreach

foreach 循环按顺序对 map<K, V> 类型的键值对(条目)进行操作:从 map 的最小键到最大键。

该循环为给定映射中的每个条目执行一个代码块,每次迭代都会捕获键和值。 该循环为给定映射中的每个条目执行一个代码块,每次迭代都会捕获键和值。 当您事先不知道map中有多少个条目,或不想明确地使用map的 .get() method 查找每个条目时,这将非常方便。

请注意,每次迭代时捕获的键和值对的名称是任意的,可以是任何有效的 Tact 标识符,只要它们是当前作用域的新标识符即可。 最常见的选项是:kv,或者 keyvalue

在下面的示例中,地图 cells44 个条目,因此循环将运行 44 次:

// Empty map
let cells: map<Int, Cell> = emptyMap();
// Setting four entries
cells.set(1, beginCell().storeUint(100, 16).endCell());
cells.set(2, beginCell().storeUint(200, 16).endCell());
cells.set(3, beginCell().storeUint(300, 16).endCell());
cells.set(4, beginCell().storeUint(400, 16).endCell());
// A variable for summing up the values
let sum: Int = 0;
// For each key and value pair in cells map, do:
foreach (key, value in cells) { // or just k, v
let s: Slice = value.beginParse(); // convert Cell to Slice
sum += s.loadUint(16); // sum the Slice values
}
dump(sum); // 1000

还可以遍历合约存储中的映射,以及作为 StructMessage 类型实例成员的映射:

import "@stdlib/deploy";
struct Fizz { oh_my: map<Int, Int> }
message Buzz { oh_my: map<Int, Int> }
contract Iterated {
oh_my: map<Int, Int>;
receive("call to iterate!") {
let oh_my: map<Int, Int> = emptyMap();
oh_my.set(0, 42);
oh_my.set(1, 27);
self.oh_my = oh_my; // assigning local map to the storage one
let fizz = Fizz{ oh_my }; // field punning
let buzz = Buzz{ oh_my }; // field punning
// Iterating over map in contract storage
foreach (key, value in self.oh_my) {
// ...
}
// Iterating over map member of a Struct Fizz instance
foreach (key, value in fizz.oh_my) {
// ...
}
// Iterating over map member of a Message Buzz instance
foreach (key, value in buzz.oh_my) {
// ...
}
}
}

请注意,与 let 语句类似,可以通过指定下划线 _ 来丢弃捕获的键或值(或两者):

// Empty map
let quartiles: map<Int, Int> = emptyMap();
// Setting some entries
quartiles.set(1, 25);
quartiles.set(2, 50);
quartiles.set(3, 75);
// Discarding captured keys
// without modifying them in the map itself
foreach (_, value in quartiles) {}
// Discarding captured values
// without modifying them in the map itself
foreach (key, _ in quartiles) {}
// Discarding both keys and values
// without modifying them in the map itself
foreach (_, _ in quartiles) {
// Can't access via _, but can do desired operations
// n times, where n is the current length of the map
}