联合体
联合体与结构体声明使用相同的语法,只是将 struct
替换为 union
。
#![allow(unused)] fn main() { #[repr(C)] union MyUnion { f1: u32, f2: f32, } }
联合体的关键特性是,所有的字段共享相同的存储空间。 因此,对联合体的一个字段的写操作可能会覆盖其它字段的值,而联合体的大小则由其最大字段的大小决定。
联合体字段类型限制为以下类型的子集:
Copy
类型- 引用 (
&T
和&mut T
为任意的T
) ManuallyDrop<T>
(为任意的T
)- 元组和数组仅允许包含联合体字段类型
这个限制确保了联合体字段不需要被丢弃。
与结构体和枚举类似,可以为联合体实现 Drop
trait 来手动定义在释放时发生的行为。
初始化联合体
联合体类型的值可以使用与结构体类型相同的语法创建,只不过必须指定一个字段:
#![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表示法 写入然后读取联合体类似于从用于写入的类型到用于读取的类型的 形变
。
因此,所有对联合体字段的读取都必须放在 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 中的标记 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, } } } }
联合体字段的引用
由于联合体字段共享存储空间,因此获得对联合体的一个字段的写访问权限可以给予对其所有其余字段的写访问权限。 借用检查规则必须调整以考虑这一点。 因此,如果联合体的一个字段被借用,则其所有其余字段在相同的生命周期内也被借用。
#![allow(unused)] fn main() { union MyUnion { f1: u32, f2: f32 } // ERROR: 不能将 `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); } }
你所看到的是,在很多方面 (除了布局、安全性和所有权) 上,联合体和结构体表现得非常相似,这是由于从结构体继承了语法形式。 这在语言中的许多未提及方面也同样适用 (例如私有性、名称解析、类型推断、泛型、trait实现、内部实现、一致性、模式检查等等) 。