Перелічувані типи

Ключове слово enum дозволяє створити тип, який має кілька різних варіантів:

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

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

fn main() {
    let m: PlayerMove = PlayerMove::Run(Direction::Left);
    println!("На цьому повороті: {:?}", m);
}
This slide should take about 5 minutes.

Ключові моменти:

  • Переліки дозволяють збирати набір значень під одним типом.
  • Напрямок - це тип з варіантами. Існує два значення Direction: Direction::Left та Direction::Right.
  • PlayerMove - це тип з трьома варіантами. На додаток до корисного навантаження, Rust зберігатиме дискримінант, щоб під час виконання знати, який варіант є у значенні PlayerMove.
  • Це може бути гарний час для порівняння структури та переліки:
    • В обох ви можете мати просту версію без полів (структура одиниць) або з різними типами полів (різні варіанти корисного навантаження).
    • Ви навіть можете реалізувати різні варіанти переліку окремими структурами, але тоді вони не будуть одного типу, як якщо б всі вони були визначені в переліку.
  • Rust використовує мінімальний обсяг пам'яті для зберігання дискримінанта.
    • Якщо потрібно, він зберігає ціле число найменшого необхідного розміру

    • Якщо допустимі значення варіантів не покривають усіх бітових шаблонів, для кодування дискримінанта буде використано неприпустимі бітові шаблони ("нішева оптимізація"). Наприклад, Option<&u8> зберігає або вказівник на ціле число, або NULL для варіанта None.

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

      #[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>>().

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

    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);
        }
    }