Enum especial Option<T>
Em Rust não temos nulo, isso mesmo a linguagem não aplica o conceito de ponteiros nulos, para dizer se algo existe ou não temos o enum Option<T>, este enum, os valores possíveis para este enum são Some(T) e None. Temos alguns métodos neste enum, como is_none, is_some, unwrap, expected, or_else, or.
Extraindo o valor de dentro de um Option<T>
Podemos extrair o valor de um Option, pelos métodos, unwrap, expect, por um match, ou por um if let. Cada modo de extrair tem sua peculiaridade, com o unwrap ou com o expect caso o valor seja None temos uma falha na aplicação e sua execução é abortada.
struct Cliente { nome: String, idade: Option<u8>, } fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; cliente.idade.unwrap(); }
Ao executar o código acima teremos a execução do programa abortada e a mensagem de erro:
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:9:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
A diferença entre o unwrape o expect, é que com o expect podemos definir uma mensagem para este erro
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; cliente.idade.expect("idade não informada"); }
O código acima causa um erro a mensagem informada.
thread 'main' panicked at 'idade não informada', src/main.rs:8:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Mas se quisermos evitar este erro, como fazemos isso? Podemos utilizar os métodos is_none ou is_some para verificar isso.
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: Some(21), }; if cliente.idade.is_some() { let idade = cliente.idade.unwrap(); println!("O cliente {} tem {} anos", cliente.nome, idade); } }
Agora temos uma checagem se o valor existe, podemos usar o is_none para adicionar um tratamento para caso a idade não exista.
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let mut cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; if cliente.idade.is_none() { println!("Idade do cliente não informada, favor informar:"); let mut buffer = String::new(); std::io::stdin().read_line(&mut buffer).unwrap(); cliente.idade = Some(buffer.trim().parse().unwrap()); } }
Mas esse talvez não seja o melhor modo de fazer isso.
Extraindo o valor com um match
O operador match pode ser utilizado para enums, lembra dos enums com valores associados? O Option é um enum com valores associados. Então podemos utilizar o match para chegar se o valor existe.
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; match cliente.idade { Some(idade) => println!("A idade do cliente {} é {}", cliente.nome, idade), None => println!("Idade do cliente {} não foi informada", cliente.nome) } }
Claro podemos utilizar de todos os aspectos do match nessa abordagem
Operador if let
O operador if let é geralmente usado para tratativas pequenas. Onde realizamos uma validação e já atribuímos o valor a uma variável. Podendo ser feito da seguinte maneira if let Some(nome ou ignora o valor) = expressao teste { codigo } else { se nao }.
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; if let Some(idade) = cliente.idade { println!("A idade do cliente {} é {}", cliente.nome, idade) } else { println!("Idade do cliente {} não foi informada", cliente.nome) } }
O código acima tem o mesmo resultado do código com o match, claro o if let também pode retornar algo, assim como o match
struct Cliente { nome: String, idade: Option<u8>, } //--declaração da struct fn main() { let cliente = Cliente { nome: "Rust4Noobs".to_string(), idade: None, }; let idade = if let Some(idade) = cliente.idade { idade } else { 34 + 35 }; println!("A idade do cliente {} é {}", cliente.nome, idade); }
E temos sucesso, caso o valor exista é retornado o valor dentro de Some(idade) caso não exista é retornado o resultado da soma de 34 + 35.
Operador while let
O operador if let tem um irmão o while let, seu comportamento é parecido, lembram da implementação de da trait Iterator que realizamos? Caso não lembre aqui esta ela.
#![allow(unused)] fn main() { struct Contador { contagem: u64 } impl Iterator for Contador { type Item = u64; fn next(&mut self) -> Option<Self::Item> { if self.contagem >= 100 { None } else { self.contagem += 1; Some(self.contagem) } } } }
Ao implementar essa trait, temos o método next que nos retorna um Option, podemos utilizar esse retorno para ir iterando o nosso contador.
struct Contador { contagem: u64 } impl Iterator for Contador { type Item = u64; fn next(&mut self) -> Option<Self::Item> { if self.contagem >= 100 { None } else { self.contagem += 1; Some(self.contagem) } } } //--declaração do contador fn main() { let mut contador = Contador { contagem: 0 }; while let Some(n) = contador.next() { println!("Contador atual {}", n) } }
O enum Option, nos da um controle muito grande, e nos possibilita vários modos de tratar valores inexistentes, vale a pena se aprofundar mais nele.