Se você já usou outra linguagem de programação (C/C++, Python, Java, Ruby, Lisp etc.), é provável que já tenha encontrado null ou nil.
É com null ou nil que essas linguagens indicam que uma determinada variável não tem valor.
No entanto, isso faz com que usar uma variável null por acidente seja um erro fácil (e frequente) de cometer.
Como você pode imaginar, tentar chamar uma função que não existe ou acessar um valor que não existe pode levar a todo tipo de bug e travamento.
O criador do null chegou ao ponto de chamá-lo de seu 'erro de um bilhão de dólares'.
Option
Para evitar esses problemas, as variáveis de Cairo podem usar o enum Option para representar com segurança a ausência de um valor.
Esse enum tem duas variantes: None, o equivalente a null em Cairo; e Some(T), em que T é um valor de qualquer tipo.
Ele se parece com isto:
enum Option<T> {
None,
Some(T),
}
Você pode pensar no Option como uma camada de segurança entre você e os problemas que as referências nulas podem causar, sem deixar de lado a utilidade conceitual delas.
Option
Definir uma variável como None é bem simples:
let nothing: Option<u32> = Option::None; // Variable nothing is set to None
No entanto, se você quiser que o tipo Option carregue um valor, não pode atribuir esse valor diretamente.
Uma variável do tipo Option e, digamos, uma variável do tipo i32 não são equivalentes.
Você precisa usar Some:
let wrong_way: Option<i32> = -4; // This will not work
let right_way: Option<i32> = Option::Some(-4); // This will work
let another_right_way = Option::Some(-4); // Compiler infers that this is Option<i32>
Pelo mesmo motivo, o seguinte não vai funcionar:
let number = 47;
let option_number = Option::Some(15);
let compile_error = number + option_number; // Cannot add an i32 and an Option<i32> - they are of different types
Para obter o valor contido em Some, você precisa primeiro verificar se ele existe:
let mut some_words = Option::Some("choose something to say"); // some_words set to something
match some_words {
Option::Some(str) => println!("Here, we will {}", str),
Option::None => println!("I've got nothing to say"),
} // Prints, "Here, we will choose something to say"
some_words = Option::None; // some_words now set to None
// Exactly the same match block as above
match some_words {
Option::Some(str) => println!("Here, we will {}", str),
Option::None => println!("I've got nothing to say"),
} // Prints, "I've got nothing to say"
Cairo tem outras ferramentas além do match para verificar e acessar valores contidos em um Option, mas o match já deve ser familiar para você agora.
Considere isto uma demonstração de por que Cairo usa Option em vez de uma referência nula.
A ideia é que você precisa verificar se a variável Option é Some (caso em que você pode extrair e usar o valor contido nela) ou None.
Caso contrário, seu programa não vai compilar; o compilador mantém você a salvo do null.
Você está trabalhando na implementação de um jogo de RPG. O personagem do jogador é representado pelo seguinte:
pub struct Player {
health: u32,
mana: Option<u32>,
level: u32,
}
Neste jogo, os jogadores precisam alcançar o nível 10 antes de desbloquear um reservatório de mana, que podem usar para conjurar feitiços.
Antes disso, a mana do Player é Option::None.
Você está trabalhando em duas funcionalidades deste jogo: o mecanismo de reviver e o mecanismo de conjurar feitiços.
O método revive deve verificar se o Player está de fato morto (a vida dele chegou a 0) e, se estiver, deve retornar uma nova instância de Player com 100 de vida.
O método revive deve preservar o nível do Player.
Se o nível do Player for 10 ou mais, ele deve ser revivido com 100 de mana.
Se o nível do Player for menor que 10, a mana dele deve ser Option::None.
let dead_player = Player { health: 0, mana: Option::None, level: 2 };
dead_player.revive()
// Returns Player { health: 100, mana: Option::None, level: 2 }
Se o método revive for chamado em um Player cuja vida seja 1 ou mais, o método deve retornar Option::None.
let alive_player = Player { health: 1, mana: Option::Some(15), level: 11 };
alive_player.revive()
// Returns Option::None
O método cast_spell recebe uma referência mutável ao Player, além de um parâmetro mana_cost que indica quanta mana o feitiço custa.
Ele retorna a quantidade de dano que o feitiço conjurado causa.
Se o jogador não tiver acesso a um reservatório de mana, tentar conjurar o feitiço deve diminuir a vida dele no valor do custo de mana do feitiço. O dano retornado deve ser 0.
let not_a_wizard_yet = Player { health: 79, mana: Option::None, level: 9 };
assert_eq!(not_a_wizard_yet.cast_spell(5), 0)
assert_eq!(not_a_wizard_yet.health, 74);
assert_eq!(not_a_wizard_yet.mana, Option::None);
Se o jogador tiver um reservatório de mana, mas mana insuficiente, o método não deve afetar o reservatório. O dano retornado deve ser 0.
let low_mana_wizard = Player { health: 93, mana: Option::Some(3), level: 12 };
assert_eq!(low_mana_wizard.cast_spell(10), 0);
assert_eq!(low_mana_wizard.health, 93);
assert_eq!(low_mana_wizard.mana, Option::Some(3));
Caso contrário, o feitiço é conjurado com sucesso.
O mana_cost deve ser descontado do reservatório de mana do Player.
O dano retornado deve ser o dobro do custo de mana do feitiço.
let wizard = Player { health: 123, mana: Option::Some(30), level: 18 };
assert_eq!(wizard.cast_spell(10), 20);
assert_eq!(wizard.health, 123);
assert_eq!(wizard.mana, Option::Some(20));
Divirta-se!
Crie sua conta no Exercism para aprender e dominar Cairo com 25 conceitos68 exercícios e mentoria humana de verdade, tudo de graça.