联合体
语法
联合体 :
union
标识符 泛型参数? WhereClause?{
结构体字段?}
联合体声明使用与结构体声明相同的语法,除了使用 union
代替 struct
。
联合体声明在其所在的模块或代码块的类型命名空间中定义给定的名称。
#![allow(unused)] fn main() { #[repr(C)] union MyUnion { f1: u32, f2: f32, } }
联合体的关键特性是联合体的所有字段共享公共存储空间。 因此,写入联合体的一个字段可能会覆盖其其他字段,并且联合体的大小由其最大字段的大小决定。
联合体字段类型限制为以下类型子集
Copy
类型
- 引用 (对于任意
T
的&T
和&mut T
)
ManuallyDrop<T>
(对于任意T
)
- 仅包含允许的联合体字段类型的元组和数组
此限制特别确保了联合体字段永远不需要被 drop。 与结构体和枚举类似,可以为联合体 impl Drop
以手动定义在它被 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; } }
通常,使用联合体的代码将提供围绕不安全联合体字段访问的安全包装器。
相反,写入联合体字段是安全的,因为它们只是覆盖任意数据,但不会导致未定义行为。(请注意,联合体字段类型永远不能有 drop glue,因此联合体字段写入永远不会隐式 drop 任何内容。)
联合体的模式匹配
访问联合体字段的另一种方法是使用模式匹配。
联合体字段上的模式匹配使用与结构体模式相同的语法,但模式必须只指定一个字段。
由于模式匹配就像使用特定字段读取联合体,因此也必须将其放在 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 语言的许多未提及的方面(例如隐私、名称解析、类型推断、泛型、trait 实现、固有实现、一致性、模式检查等等)。