联合体

语法

联合体 :

   union 标识符 泛型参数? Where 子句? {结构体字段 }

联合体声明使用与结构体声明相同的语法,只是将 struct 替换为 union

#![allow(unused)]
fn main() {
#[repr(C)]
union MyUnion {
    f1: u32,
    f2: f32,
}
}

联合体的关键特性是所有字段共享相同的存储空间。因此,写入联合体的一个字段可能会覆盖其其他字段,并且联合体的大小由其最大字段的大小决定。

联合体字段类型仅限于以下类型的子集

  • Copy 类型
  • 引用(对于任意 T,可以使用 &T&mut T
  • ManuallyDrop<T>(对于任意 T
  • 仅包含允许的联合体字段类型的元组和数组

此限制尤其确保永远不需要删除联合体字段。与结构体和枚举一样,可以为联合体 impl Drop 以手动定义其被删除时的行为。

联合体的初始化

可以使用与结构体类型相同的语法创建联合体类型的值,只是它必须精确指定一个字段

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

let u = MyUnion { f1: 1 };
}

上面的表达式创建了一个 MyUnion 类型的值,并使用字段 f1 初始化存储空间。可以使用与结构体字段相同的语法访问联合体

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

let u = MyUnion { f1: 1 };
let f = unsafe { u.f1 };
}

读取和写入联合体字段

联合体没有“活动字段”的概念。相反,每次联合体访问都只是将存储空间解释为用于访问的字段的类型。读取联合体字段会读取该字段类型在联合体中的位。字段可能具有非零偏移量(除非使用 C 表示形式);在这种情况下,将读取从字段偏移量开始的位。程序员有责任确保数据在字段类型处有效。否则会导致 未定义行为。例如,从 布尔类型 的字段中读取值 3 是未定义行为。实际上,使用 C 表示形式 向联合体写入数据然后从中读取数据类似于从用于写入的类型到用于读取的类型的 transmute

因此,所有对联合体字段的读取都必须放在 unsafe 块中

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
let u = MyUnion { f1: 1 };

unsafe {
    let f = u.f1;
}
}

通常,使用联合体的代码将提供围绕不安全联合体字段访问的安全包装器。

相反,写入联合体字段是安全的,因为它们只是覆盖任意数据,但不会导致未定义行为。(请注意,联合体字段类型永远不会有删除胶水,因此联合体字段写入永远不会隐式删除任何内容。)

对联合体进行模式匹配

访问联合体字段的另一种方法是使用模式匹配。对联合体字段进行模式匹配使用与结构体模式相同的语法,只是模式必须精确指定一个字段。由于模式匹配就像使用特定字段读取联合体一样,因此它也必须放在 unsafe 块中。

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }

fn f(u: MyUnion) {
    unsafe {
        match u {
            MyUnion { f1: 10 } => { println!("ten"); }
            MyUnion { f2 } => { println!("{}", f2); }
        }
    }
}
}

模式匹配可以将联合体匹配为较大结构的字段。特别是,当使用 Rust 联合体通过 FFI 实现 C 标记联合体时,这允许同时匹配标记和相应的字段

#![allow(unused)]
fn main() {
#[repr(u32)]
enum Tag { I, F }

#[repr(C)]
union U {
    i: i32,
    f: f32,
}

#[repr(C)]
struct Value {
    tag: Tag,
    u: U,
}

fn is_zero(v: Value) -> bool {
    unsafe {
        match v {
            Value { tag: Tag::I, u: U { i: 0 } } => true,
            Value { tag: Tag::F, u: U { f: num } } if num == 0.0 => true,
            _ => false,
        }
    }
}
}

对联合体字段的引用

由于联合体字段共享相同的存储空间,因此获得对联合体一个字段的写访问权限可以获得对其所有剩余字段的写访问权限。借用检查规则必须进行调整以考虑到这一事实。因此,如果借用了联合体的一个字段,则其所有剩余字段也会在相同的生命周期内被借用。

#![allow(unused)]
fn main() {
union MyUnion { f1: u32, f2: f32 }
// ERROR: cannot borrow `u` (via `u.f2`) as mutable more than once at a time
fn test() {
    let mut u = MyUnion { f1: 1 };
    unsafe {
        let b1 = &mut u.f1;
//                    ---- first mutable borrow occurs here (via `u.f1`)
        let b2 = &mut u.f2;
//                    ^^^^ second mutable borrow occurs here (via `u.f2`)
        *b1 = 5;
    }
//  - first borrow ends here
    assert_eq!(unsafe { u.f1 }, 5);
}
}

如您所见,在许多方面(布局、安全性和所有权除外),联合体的行为与结构体完全相同,这很大程度上是由于它们从结构体继承了语法形状。对于 Rust 语言的许多未提及的方面(例如私有性、名称解析、类型推断、泛型、特征实现、固有实现、一致性、模式检查等)也是如此。