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)
  • Кортежи и массивы, содержащие только разрешённые типы полей объединения

Это ограничение гарантирует, в частности, что поля объединения никогда не нуждаются в освобождении памяти. Как и для структур и перечислений, можно реализовать 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 для реализации размеченного объединения C через FFI, это позволяет одновременно сопоставлять метку и соответствующее поле:

#![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 }
// ОШИБКА: нельзя заимствовать `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 (таких как приватность, разрешение имён, вывод типов, обобщённое программирование, реализации трейтов, собственные реализации, когерентность, проверка образцов и т.д., т.д., т.д.).