声明与绑定

在 Flurry 中,我们将值与名称相关联的过程称为绑定 (Binding)。通过绑定,我们可以方便地引用和操作数据。Flurry 提供了两种主要的绑定方式:let 用于变量绑定,const 用于常量绑定。

变量绑定 (let)

使用 let 关键字可以创建一个变量绑定。

let message = "Hello, Flurry!";
let count = 0;
let pi: f64 = 3.14159; -- 显式类型注解
  • 类型推断: Flurry 通常支持类型推断。如果初始化表达式的类型可以明确推断出来,你可以省略类型注解(如 messagecount)。
  • 显式注解: 你也可以使用 : 后跟类型来显式指定变量的类型(如 pi)。在函数签名、结构体字段等需要明确类型的地方,类型注解通常是必需的。

可变性 (Mutability):

与某些强制区分可变与不可变绑定的语言(如 Rust 的 let vs let)不同,Flurry(类似于 Zig 的设计思路)在 let 绑定层面可能不严格强制区分可变性。这意味着通过 let 绑定的变量默认可能是可变的

let counter = 0;
counter = counter + 1; -- 假设 let 默认允许修改

-- 如果需要强制不可变绑定,语言可能有其他机制,
-- 或者依赖于后续的使用方式分析(例如,传递不可变引用)。
-- 目前我们假设 let 绑定允许后续赋值。

TODO: 明确 Flurry let 的确切可变性语义。是默认可变,还是有单独的 var 关键字,或者通过其他方式控制?当前暂按“默认可变”或“不强制区分”处理。

这种设计选择旨在简化变量声明,将可变性控制的重心更多地放在类型系统(例如,可变引用 *mut T vs. 不可变引用 *T)和副作用分析上。

常量绑定 (const)

使用 const 关键字可以创建一个常量绑定。常量与变量的关键区别在于:

  1. 编译时求值: const 绑定的值必须在编译时就能完全确定。初始化表达式必须是一个编译时常量表达式。
  2. 完全不可变: 常量的值在程序运行期间不能被改变。它们通常会被编译器直接内联或放入只读内存段。
  3. 类型注解通常需要: 常量的类型通常需要显式注解,因为编译器不会像对 let 那样进行复杂的类型推断(尽管简单的字面量类型可能可以推断)。
const MAX_USERS: usize = 1000;
const DEFAULT_TIMEOUT: Duration = Duration.seconds(5); -- 假设 Duration.seconds 是 comptime 函数
const APP_NAME: str = "Flurry Core System"; -- 编译时字符串

-- const RUNTIME_VALUE: i32 = get_runtime_input(); -- 非法,初始化器不是编译时常量

常量非常适合用于定义程序中不变的配置值、标志、或者需要在编译时进行计算和使用的值。它们是 Flurry 强大的 comptime 能力的基础体现之一。

作用域与遮蔽 (Scope & Shadowing)

Flurry 中的绑定遵循词法作用域规则。一个绑定只在其声明的代码块(及其子块)内有效。

fn main() {
    let x = 10;
    { -- 进入新的作用域
        let y = 20;
        println("Inner: x = {i32}, y = {i32}", x, y); -- x 和 y 都可见
        let x = 5; -- 在内层作用域 "遮蔽" 外层的 x
        println("Inner (shadowed): x = {i32}", x); -- 输出 5
    } -- y 在此离开作用域
    -- println("Outer: y = {}", y); -- 错误,y 不在此作用域
    println("Outer: x = {i32}", x); -- 输出 10,内层的 x 遮蔽结束
}

Flurry 允许在内部作用域使用 let 重新声明一个同名变量,这会遮蔽 (Shadow) 外部作用域的同名变量。被遮蔽的变量在内部作用域内暂时不可访问,直到内部作用域结束。遮蔽是一个有用的特性,例如用于类型转换或值的重新绑定,但过度使用可能会降低代码清晰度。

理解 letconst 的区别以及作用域规则,是管理程序状态和数据生命周期的基础。