联合体 (Unions)

联合体(Union)是一种特殊的数据结构,它允许其多个字段共享同一块内存区域。这意味着在任何时候,联合体实例只能有效地持有其一个字段的值。联合体主要用于与 C 语言代码交互 (FFI),或者在需要极度节省内存且能安全管理活跃字段的底层编程场景。

警告: 使用联合体需要开发者自行承担跟踪哪个字段当前是活跃(有效)的责任。错误地访问非活跃字段可能导致未定义行为或内存损坏。因此,除非有充分理由并能确保安全,否则应优先使用枚举(Enum)来表示“多种可能之一”的数据。

定义联合体

使用 union 关键字定义联合体,并在花括号 {} 内声明其所有可能的字段。

-- 一个可能持有整数或浮点数的联合体
union IntOrFloat {
    i: i32,
    f: f32,
}

-- 用于模拟 C 语言联合体进行 FFI
^repr(.c) -- (假设) 指定 C 兼容布局
union CEventArgs {
    key_event: KeyEvent, -- 假设 KeyEvent 是一个 struct
    mouse_event: MouseEvent, -- 假设 MouseEvent 是一个 struct
}

实例化和访问联合体

联合体的实例化通常需要明确指定要初始化的那个字段。访问字段也使用点 (.) 操作符,但必须确保访问的是当前活跃的字段。

test {
    -- 初始化为整数
    let value = IntOrFloat { .i 10 }

    -- 安全地访问整数(因为我们知道它是活跃的)
    println("Integer value: {}", value.i);

    -- **危险操作**:写入浮点字段会覆盖整数数据
    value.f = 3.14;

    -- **危险操作**:此时再读取 i 字段会得到未定义或损坏的数据
    -- println("Integer value after float write: {}", value.i); -- <-- 未定义行为!

    -- 安全地访问浮点数
    println("Float value: {}", value.f);
}

安全使用联合体:带标签的联合体 (Tagged Unions)

为了安全地使用联合体,常见的模式是将其与一个枚举(作为标签)组合在一个结构体中,用标签来明确指示当前哪个联合体字段是活跃的。Flurry 的枚举 (Enum) 本身就是一种更安全、更推荐的带标签联合体的实现方式。

-- 使用枚举代替裸联合体是更安全的方式
enum SafeIntOrFloat {
    integer(i32),
    float(f32),
}

test {
    let value = SafeIntOrFloat.integer(10);

    match value {
        .integer(i) => println("Integer: {}", i),
        .float(f) => println("Float: {}", f),
    }
}

联合体作为命名空间与关联函数

structenum 一样,union 定义也是一个命名空间,可以在其内部定义关联函数。但这通常不太常见,因为操作联合体实例需要外部信息(哪个字段是活跃的)。

总结

联合体提供了底层内存布局的控制能力,允许多个字段共享内存,主要用于 FFI 和特殊的内存优化场景。然而,由于其固有的不安全性(需要手动跟踪活跃字段),强烈建议优先使用枚举 (Enum) 来表示互斥的数据变体。如果必须使用联合体,务必采取额外的机制(如外部标签)来确保访问安全。