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

Перечисления

Ключевое слово enum позволяет создать тип, у которого есть несколько разных вариантов:

// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0

#[derive(Debug)]
enum Direction {
    Left,
    Right,
}

#[derive(Debug)]
enum PlayerMove {
    Pass,                        // Простой вариант
    Run(Direction),              // Вариант-кортеж
    Teleport { x: u32, y: u32 }, // Вариант-структура
}

fn main() {
    let dir = Direction::Left;
    let player_move: PlayerMove = PlayerMove::Run(dir);
    println!("On this turn: {player_move:?}");
}
This slide should take about 5 minutes.

Ключевые моменты:

  • Перечисления позволяют объединить набор значений в один тип.
  • Direction — это тип с вариантами. У Direction есть два значения: Direction::Left и Direction::Right.
  • PlayerMove — это тип с тремя вариантами. Помимо данных вариантов, Rust хранит дискриминант, чтобы во время выполнения знать, какой вариант находится в значении PlayerMove.
  • Сейчас хорошее время сравнить структуры и перечисления:
    • И там, и там можно иметь простую версию без полей (структура-единица) или версию с полями разных типов (данные вариантов).
    • Можно даже реализовать разные варианты перечисления отдельными структурами, но тогда они не будут одним и тем же типом, как если бы все они были определены в одном перечислении.
  • Rust использует минимум памяти для хранения дискриминанта.
    • При необходимости он хранит целое минимально необходимого размера.

    • Если допустимые значения вариантов не покрывают все битовые шаблоны, Rust использует недопустимые битовые шаблоны для кодирования дискриминанта (это «оптимизация ниш», niche optimization). Например, Option<&u8> хранит либо указатель на целое, либо NULL для варианта None.

    • При необходимости можно управлять дискриминантом (например, для совместимости с C):

      // Copyright 2023 Google LLC
      // SPDX-License-Identifier: Apache-2.0
      
      #[repr(u32)]
      enum Bar {
          A, // 0
          B = 10000,
          C, // 10001
      }
      
      fn main() {
          println!("A: {}", Bar::A as u32);
          println!("B: {}", Bar::B as u32);
          println!("C: {}", Bar::C as u32);
      }

      Без repr тип дискриминанта занимает 2 байта, так как 10001 помещается в 2 байта.

Для дальнейшего изучения

У Rust есть несколько оптимизаций, которые позволяют перечислениям занимать меньше места.

  • Оптимизация нулевого указателя: для некоторых типов Rust гарантирует, что size_of::<T>() равен size_of::<Option<T>>().

    Пример кода, если вы хотите показать, как побитовое представление может выглядеть на практике. Важно отметить, что компилятор не даёт никаких гарантий относительно этого представления, поэтому такой код полностью небезопасен.

    // Copyright 2023 Google LLC
    // SPDX-License-Identifier: Apache-2.0
    
    use std::mem::transmute;
    
    macro_rules! dbg_bits {
        ($e:expr, $bit_type:ty) => {
            println!("- {}: {:#x}", stringify!($e), transmute::<_, $bit_type>($e));
        };
    }
    
    fn main() {
        unsafe {
            println!("bool:");
            dbg_bits!(false, u8);
            dbg_bits!(true, u8);
    
            println!("Option<bool>:");
            dbg_bits!(None::<bool>, u8);
            dbg_bits!(Some(false), u8);
            dbg_bits!(Some(true), u8);
    
            println!("Option<Option<bool>>:");
            dbg_bits!(Some(Some(false)), u8);
            dbg_bits!(Some(Some(true)), u8);
            dbg_bits!(Some(None::<bool>), u8);
            dbg_bits!(None::<Option<bool>>, u8);
    
            println!("Option<&i32>:");
            dbg_bits!(None::<&i32>, usize);
            dbg_bits!(Some(&0i32), usize);
        }
    }