Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

联合体

Syntax
Union
    union IDENTIFIER GenericParams? WhereClause? { StructFields? }

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

联合体声明在其所在的模块或块的类型命名空间中定义给定名称。

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

联合体的关键属性是联合体的所有字段共享公共存储。因此,对联合体一个字段的写入可以覆盖其其他字段,联合体的大小由其最大字段的大小决定。

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

  • Copy 类型
  • 引用(&T&mut T,对于任意 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); }
        }
    }
}
}

模式匹配可以将联合体匹配为更大结构的字段。特别是,当通过 FFI 使用 Rust 联合体实现 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 语言的许多未提及方面(如隐私、名称解析、类型推断、泛型、trait 实现、固有实现、一致性、模式检查等等等等)也是如此。