Unions
联合体
unions.md
commit: b10666f10f6a731cfffbfc3c42841c59f8f76b58
本章译文最后维护日期:2024-06-15
句法
Union :
union
IDENTIFIER GenericParams? WhereClause?{
StructFields?}
除了用 union
代替 struct
外,联合体声明使用和结构体声明相同的句法。
#![allow(unused)] fn main() { #[repr(C)] union MyUnion { f1: u32, f2: f32, } }
联合体的关键特性是联合体的所有字段共享同一段存储。因此,对联合体的一个字段的写操作会覆盖其他字段,而联合体的内存宽度由其内存宽度最大的字段的内存宽度所决定。
联合体字段类型仅限于以下类型子集:
Copy
类型- 引用 (任意
T
上的&T
和&mut T
) ManuallyDrop<T>
(任意T
)- 仅包含了以上被允许的联合体字段类型的元组或数组
这个限制专门用来确保联合体的字段永远不需要销毁操作。与结构体和枚举一样,可以对联合体使用 impl Drop
来手动定义被销毁时发生的操作。
编译器不接受没有任何字段的联合体,但宏可以接受。
Initialization of a union
联合体的初始化
可以使用与结构体类型相同的句法创建联合体类型的值,但必须只能指定一个字段:
#![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 }; }
Reading and writing union fields
读写联合体字段
联合体没有“活跃成员字段(active field)”的概念。
相反,每次访问联合体只是将此联合体的存储解释为此访问的字段类型。
读取联合体的字段就是以当前读取字段的类型来解读此联合体的存储位。
字段可以有一个非零的偏移量存在(使用 C表型的除外);在这种情况下,读取将从字段的相对偏移量的 bit 开始。
程序员有责任确保此数据在当前字段类型下有效。
否则会导致未定义行为(undefined behavior)。
例如,在 bool
类型的字段下读取到数值 3
是未定义行为。
实际上,对一个 C表型的联合体进行写操作,然后再从中读取,就好比从用于写入的类型到用于读取的类型的 transmute
操作。
因此,所有的联合体字段的读取必须放在 unsafe
块里:
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } let u = MyUnion { f1: 1 }; unsafe { let f = u.f1; } }
通常,那些用到联合体的程序代码会先在非安全的联合体字段访问操作上提供一层安全包装,然后再使用。
相反,对联合体字段的写入操作是安全的,因为它们只是覆盖任意数据,不会导致未定义行为。(请注意,联合体字段类型不会关联到 drop操作,因此联合字段的写入永远不会隐式销毁任何内容。)
Pattern matching on unions
联合体上的模式匹配
访问联合体字段的另一种方法是使用模式匹配。联合体字段上的模式匹配与结构体上的模式匹配使用相同的句法,只是这种模式只能一次指定一个字段。因为模式匹配就像使用特定字段来读取联合体,所以它也必须被放在非安全(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标签联合体(C tagged union)时,这允许同时在标签和相应字段上进行匹配:
#![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, } } } }
References to union fields
引用联合体字段
由于联合体字段共享存储,因此拥有对联合体一个字段的写访问权就同时拥有了对其所有其他字段的写访问权。因为这一事实,引用的借用检查规则必须调整。因此,如果联合体的一个字段是被出借,那么在相同的生存期内它的所有其他字段也都处于出借状态。
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } // 错误: 不能同时对 `u` (通过 `u.f2`)拥有多余一次的可变借用 fn test() { let mut u = MyUnion { f1: 1 }; unsafe { let b1 = &mut u.f1; // ---- 首次可变借用发生在这里 (通过 `u.f1`) let b2 = &mut u.f2; // ^^^^ 二次可变借用发生在这里 (通过 `u.f2`) *b1 = 5; } // - 首次借用在这里结束 assert_eq!(unsafe { u.f1 }, 5); } }
如您所见,在许多方面(除了布局、安全性和所有权),联合体的行为与结构体完全相同,这很大程度上是因为联合体继承使用了结构体的句法的结果。对于 Rust 语言未明确提及的许多方面(比如隐私性(privacy)、名称解析、类型推断、泛型、trait实现、固有实现、一致性、模式检查等等)也是如此。