Files
playbook/skills/tsl-syntax-reference/references/04_variables_and_constants.md
T
2026-08-13 15:37:54 +08:00

454 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TSL 变量与常量
这一篇收拢变量与常量初始化规则。
## 本篇职责
<!-- section-id: syntax-04-001 -->
回答“普通变量怎样直接使用、`var` 在什么位置出现、常量必须怎样初始化、`global` 怎样跨函数共享,以及运行时常量怎样冻结一次调用中的表达式结果”。
## 核心规则
<!-- section-id: syntax-04-002 -->
- 默认变量模型是“直接赋值即得到变量”,不要求先写 `var`
- 例如:`a := 1; b := array(1, 2, 3);` 这种写法可直接编译。
- `var name;` 只作为显式声明写法,不能当成默认必需步骤。
- `const name = value;` 是常量初始化的默认生成写法;`const` 不能只声明名字而不初始化。
- 文档规则:
- `const name = value;` 适合顶层脚本常量、函数内部 `const` 段、`unit` 接口常量、类成员常量。
- 顶层脚本里的 `const name = value;` 可以放在后续脚本语句之前;单独只写一行 `const name = value;` 不作为可运行脚本骨架。
- 函数内部 `const = expr`、以及松散脚本顶层的 `const = expr`,都可以把右侧写成常量表达式。
-`const =` 初始化的这些常量不能再次赋值。
- 多参数赋值文档明确写法是 `[a, b] := array(...)`
- 左侧只有一个变量时,末尾逗号不能省略,必须写成 `[a, ] := array(...)`
- 当左侧变量数大于右侧数组长度时,多出的变量会得到 `nil`
- 右侧数组元素也可以是数组;拆出来的变量会直接得到对应子数组。
- 多参数赋值也可以出现在函数调用参数里。
- `{$explicit+}` 开启后,后续变量必须先用 `var` 声明;未声明变量会报 `variable not defined`
- `global x, y;` 声明当前任务中的全局变量;每个需要访问它的函数都要再次写 `global` 声明。
- `const name := expression;` 是运行时常量:进入所属函数时计算,当前调用内不可再次赋值;不要与编译时常量 `const name = expression;` 混写。
## 可直接照写示例
<!-- section-id: syntax-04-003 -->
使用这些示例时遵守:
- 默认生成普通变量时直接用 `:=`;只有用户明确要求或 `{$explicit+}` 场景才复制 `var`
- 普通示例默认按 `.tsl` 脚本语句区书写;需要函数或类型时,放在后置声明区。
- 复制常量示例时只使用 `const name = value;`
### 普通变量与显式声明
<!-- section-id: syntax-04-004 -->
<!-- tags: 怎么声明变量, 变量要不要先定义, 直接赋值, var 用不用写 -->
默认变量模型:
代码块身份:可直接照写示例
```tsl
a := 1;
b := array(1, 2, 3);
writeLn(a);
writeLn(b[1]);
```
代码块身份:输出片段
```text
1
2
```
显式 `var` 写法:
代码块身份:可直接照写示例
```tsl
var a;
a := 1;
```
`{$explicit+}` 下的显式声明:
代码块身份:可直接照写示例
```tsl
{$explicit+}
var a;
a := 1;
writeLn(a);
```
代码块身份:输出片段
```text
1
```
### 常量初始化
<!-- section-id: syntax-04-005 -->
<!-- tags: 定义常量, 只读的值, 不能改的值, const 怎么写 -->
顶层最稳的常量写法:
代码块身份:可直接照写示例
```tsl
const kValue = 1;
echo kValue;
```
代码块身份:输出片段
```text
1
```
顶层 `const =` 也可以写常量表达式:
代码块身份:可直接照写示例
```tsl
const kValue = 1 + 2 * 3;
writeLn(kValue);
```
代码块身份:输出片段
```text
7
```
函数内部 `const` 段:
代码块身份:可直接照写示例
```tsl
function Demo();
const max_retries = 1 + 2 * 3;
begin
return max_retries;
end;
```
`unit` 接口常量:
代码块身份:可直接照写示例
```tsl
unit DemoUnit;
interface
const kValue = 1;
function GetValue();
implementation
function GetValue();
begin
return kValue;
end;
end.
```
类成员常量:
代码块身份:可直接照写示例
```tsl
type DemoType = class
public
const kValue = 1;
end;
```
顶层 `const =` 只有在“后面继续接可执行语句”的脚本模型里才成立:
代码块身份:可直接照写示例
```tsl
const kMaxRetries = 3 + 4;
value := kMaxRetries;
```
### 全局变量 `global`
<!-- section-id: syntax-04-009 -->
<!-- tags: 全局变量, 跨函数共享变量, 不同函数访问同一变量, global 声明 -->
`global` 让同一运行任务中的不同函数访问同一变量。每个需要读写该变量的函数都要声明它;漏写时,同名标识符会按局部变量处理。
代码块身份:可直接照写示例
```tsl
global shared_value;
shared_value := 37;
writeLn(ReadShared());
function ReadShared();
begin
global shared_value;
return shared_value;
end;
```
代码块身份:输出片段
```text
37
```
本节只拥有“当前任务内跨函数共享”和“每个引用函数都要声明”的规则;不要从中推断跨任务持久化、线程隔离或进程级生命周期。
### 运行时常量 `const name := expression`
<!-- section-id: syntax-04-010 -->
<!-- tags: 运行时常量, 表达式常量, 每次调用重新计算, 初始化后不能修改 -->
运行时常量使用 `:=` 初始化,可以依赖参数、变量或函数调用。它在每次进入所属函数时重新计算一次,随后在本次调用内不可修改。
代码块身份:可直接照写示例
```tsl
writeLn(FreezeValue(2));
writeLn(FreezeValue(5));
function FreezeValue(value);
begin
const fixed := value + 1;
return fixed;
end;
```
代码块身份:输出片段
```text
3
6
```
边界:
- 编译时常量写 `const name = expression;`;运行时常量写 `const name := expression;`
- 运行时常量声明放在函数 `begin ... end` 语句体中,不放在函数头后的 `const` 声明段
- 运行时常量不作为类成员写法
- 初始化后再次赋值会破坏常量约束,不要生成
### 多参数赋值
<!-- section-id: syntax-04-006 -->
<!-- tags: 一次赋多个变量, 解构赋值, 拆数组到变量, 同时取两个值 -->
多参数赋值:
代码块身份:可直接照写示例
```tsl
[r1, r2] := array(1, 3, 5, 7, 9);
writeLn(r1);
writeLn(r2);
```
代码块身份:输出片段
```text
1
3
```
单变量拆包时,末尾逗号不能省略:
代码块身份:可直接照写示例
```tsl
[re, ] := array(1, 2, 3, 4);
writeLn(re);
```
代码块身份:输出片段
```text
1
```
左侧变量比右侧数组更长时,多出的变量为 `nil`
代码块身份:可直接照写示例
```tsl
[r1, r2] := array(1);
writeLn(r1);
writeLn(r2 = nil);
```
代码块身份:输出片段
```text
1
1
```
右侧元素也可以是数组:
代码块身份:可直接照写示例
```tsl
[r1, r2] := array((1, 2), (3, 4));
writeLn(r1[0]);
writeLn(r1[1]);
writeLn(r2[0]);
writeLn(r2[1]);
```
结果说明:
- 依次输出 `1``2``3``4`
代码块身份:输出片段
```text
1
2
3
4
```
函数返回数组后也可以直接拆包:
代码块身份:可直接照写示例
```tsl
[error, re] := PairAdd(3, 4);
writeLn(error);
writeLn(re);
function PairAdd(a, b);
begin
return array(0, a + b);
end;
```
结果说明:
- 依次输出 `0``7`
代码块身份:输出片段
```text
0
7
```
函数参数里也可以使用多参数赋值:
代码块身份:可直接照写示例
```tsl
writeLn(Test(e := 3, [f, g] := array(1, 2), g));
function Test(a, b, c);
begin
return a + b + c;
end;
```
结果说明:
- 输出 `6`
代码块身份:输出片段
```text
6
```
## 默认生成模板
<!-- section-id: syntax-04-007 -->
普通变量和顶层常量的默认骨架如下:
代码块身份:可直接照写示例
```tsl
const kMaxRetries = 3;
counter := kMaxRetries;
items := array(1, 2, 3);
```
## 禁止项
<!-- section-id: syntax-04-008 -->
- 以为普通变量必须先写 `var` 才能使用。
- 把常量初始化写成普通变量赋值风格。
- 以为 `const =` 顶层单独写一行就一定成立。
- 以为 `const` 可以只声明名字,不写初始化表达式。
- 以为单变量拆包可以写成 `[a] := array(...)`
- 以为 `{$explicit+}` 开启后仍然可以继续直接写未声明变量。
- 只在脚本顶层声明一次 `global`,却忘记在读取它的函数里再次声明。
- 把运行时常量 `const name := expression;` 写进函数头后的编译时 `const` 声明段。
- 把运行时常量当成可重新赋值的普通变量或类成员。
代码块身份:反例 / 不可照写
```text
const value = 1;
value := 2;
```
上面这类写法会编译失败,报错点在重新赋值这一行。
代码块身份:反例 / 不可照写
```text
const value = 1;
```
上面这类“顶层单独一行”的 `const =` 也会编译失败。
代码块身份:反例 / 不可照写
```text
const value;
```
上面这类没有初始化表达式的 `const` 会编译失败;`const` 必须写初始化值。
错误原因是常量语句缺少初始化表达式。
代码块身份:反例 / 不可照写
```text
[re] := array(1, 2, 3);
```
上面这种单变量拆包写法会报 `left side can not be assign to`。单变量时必须写成 `[re, ] := ...`
代码块身份:反例 / 不可照写
```text
{$explicit+}
a := 1;
```
上面这类写法会编译失败,主因是 `variable not defined`
代码块身份:输出片段
```text
variable not defined
```