Enums (Enumerações)
A palavra-chave enum
permite a criação de um tipo que possui algumas variantes diferentes:
#[derive(Debug)] enum Direction { Left, Right, } #[derive(Debug)] enum PlayerMove { Pass, // Variante simples Run(Direction), // Variante tupla Teleport { x: u32, y: u32 }, // Variante struct } fn main() { let m: PlayerMove = PlayerMove::Run(Direction::Left); println!("Nesta rodada: {:?}", m); }
Pontos Chave:
- Enumerações permitem coletar um conjunto de valores em um tipo.
Direction
é um tipo com variantes. Existem dois valores deDirection
:Direction::Left
eDirection::Right
.PlayerMove
é um tipo com três variantes. Além dos payloads, o Rust armazenará um discriminante para que ele saiba em tempo de execução qual variante está em um valorPlayerMove
.- Este pode ser um bom momento para comparar structs e enums:
- Em ambos, você pode ter uma versão simples sem campos (unit struct, ou estrutura unitária) ou uma com diferentes tipos de campo (variant payloads ou cargas de variante).
- Você pode até mesmo implementar as diferentes variantes de uma enum com structs separadas, mas elas não seriam do mesmo tipo, como seriam se todas fossem definidas em uma enum.
- O Rust usa espaço mínimo para armazenar o discriminante.
-
Se necessário, armazena um inteiro do menor tamanho necessário
-
Se os valores de variante permitidos não cobrirem todos os padrões de bits, ele usará padrões de bits inválidos para codificar o discriminante (a "otimização de nicho"). Por exemplo,
Option<&u8>
armazena um ponteiro para um inteiro ouNULL
para a varianteNone
. -
É possível controlar o discriminante se necessário (p.ex., para compatibilidade com 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); }
Sem
repr
, o tipo do discriminante usa 2 bytes, porque 10001 cabe em 2 bytes.
-
Mais para Explorar
O Rust tem várias otimizações que pode empregar para fazer com que as enums ocupem menos espaço.
-
Otimização de ponteiro nulo: para alguns tipos, o Rust garante que
size_of::<T>()
é igual asize_of::<Option<T>>()
.Código de exemplo caso queira mostrar como a representação em bits pode ser na prática. É importante apontar que o compilador não oferece nenhuma garantia a respeito dessa representação, portanto isso é completamente inseguro.
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); } }