语句
以下语句可出现在 function 主体的任何位置。
let
语句
let
语句允许声明局部变量和 block-scoped 变量。
在Tact中,声明一个局部变量总是需要一个初始值。 但是,也可以省略类型标注,Tact 会尝试从初始值推断类型:
let value: Int = 123; // full declaration with type and valuelet vInferred = 123; // inferred type Int
let vExplicitCtx: Context = context(); // explicit type Context, a built-in Structlet vCtx = context(); // inferred type Context
请注意,null
的初始值既可以指具有任意 K
和 V
类型的空map<K, V>
,也可以指故意不为optional类型设置任何其他值。 这就是为什么在声明 optional 或 map<K, V>
时,需要明确指定类型,因为无法推断:
let vOptional: Int? = null; // explicit type Int or nulllet vOptInt = 42; // implicit type IntvOptInt = 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 inferencelet _: 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
块语句用于组合零个或多个语句。 块语句用于组合零个或多个语句。 块由一对大括号(“大括号”、{}
)分隔,包含一个由零个或多个语句和声明组成的列表。
某些语句,如 let
或 return
,必须以结束分号 ;
结束。 不过,语块中最后一条语句的分号是可选的,可以省略。
{ // <- 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; // declarationvalue = 5; // assignmentvalue += 5; // augmented assignment (one of the many, see below)
解构赋值
Available since Tact 1.6 (not released yet)解构赋值是一种简洁的方法,可以将结构和消息解包为不同的变量。 它镜像了实例化语法,但不是创建新的结构体或消息,而是将每个字段或部分字段绑定到它们各自的变量。
该语法源于 let
语句,它不是直接指定变量名,而是在赋值运算符 =
的左侧指定结构类型,与右侧值的结构类型相对应。
// Definition of Examplestruct Example { number: Int }
// An arbitrary helper functionfun 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}
请注意,绑定的顺序无关紧要——所有字段都保留其名称下的值和类型,不管它们在各自的 Struct 或 Message 中在定义中的顺序。
// "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
循环执行一段代码指定的次数。 重复次数应以正 -bit Int
的形式给出,范围从 到 2^31 - 1$。 如果数值更大,则会出现退出码 5,“整数超出预期范围 “的错误。
如果指定的重复次数等于 或包含范围 至 中的任何负数,则忽略该值,不执行代码块。
let twoPow: Int = 1;
// Repeat exactly 10 timesrepeat (10) { twoPow *= 2;}
// Skippedrepeat (-1) { twoPow *= 3333;}
twoPow; // 1024
while
只要给定条件为 true
,while
循环就会继续执行代码块。
在下面的示例中,每次迭代时,x
的值都会递减 ,因此循环将运行 次:
let x: Int = 10;while (x > 0) { x -= 1;}
do...until
do...until
循环是一个后测试循环,它至少执行一次代码块,然后继续执行,直到给定条件变为 true
。
在下面的示例中,每次迭代时,x
的值都会递减 ,因此循环将运行 次:
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 标识符,只要它们是当前作用域的新标识符即可。 最常见的选项是:k
和 v
,或者 key
和 value
。
在下面的示例中,地图 cells
有 个条目,因此循环将运行 次:
// Empty maplet cells: map<Int, Cell> = emptyMap();
// Setting four entriescells.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 valueslet 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
还可以遍历合约存储中的映射,以及作为 Struct 或 Message 类型实例成员的映射:
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 maplet quartiles: map<Int, Int> = emptyMap();
// Setting some entriesquartiles.set(1, 25);quartiles.set(2, 50);quartiles.set(3, 75);
// Discarding captured keys// without modifying them in the map itselfforeach (_, value in quartiles) {}
// Discarding captured values// without modifying them in the map itselfforeach (key, _ in quartiles) {}
// Discarding both keys and values// without modifying them in the map itselfforeach (_, _ in quartiles) { // Can't access via _, but can do desired operations // n times, where n is the current length of the map}