Zettelkasten
Section 1
Review Options

Revisão de Option e Tratamento de Option

Um dos principais princípios de Rust é remover comportamento indefinido do seu código.

Uma maneira de ocorrer comportamento indefinido é permitindo que estados como existam. Rust previne isso fazendo com que o usuário trate explicitamente todos os casos, e é aqui que entra a criação do tipoOption. Dedique um momento para revisar a seção sobre Option (opens in a new tab) do livro de Rust, se necessário.

A API do BTreeMap usa um Option ao ler valores do mapa, já que pode ser que você peça para ler o valor de alguma chave que você não definiu. Por exemplo:

use std::collections::BTreeMap;
 
let mut map = BTreeMap::new();
map.insert("alice", 100);
assert_eq!(map.get(&"alice"), Some(&100));
assert_eq!(map.get(&"bob"), None);

Uma vez que temos um tipo Option, existem muitas maneiras diferentes de interagir com ele usando Rust.

A maneira mais verbosa é usando uma declaração de match:

let maybe_value = map.get(&"alice");
match maybe_value {
    Some(value) => {
        // fazer algo com o `value`
    },
    None => {
        // talvez retornar um erro já que não havia valor lá
    }
}

🚨 Alerta: O que você NÃO DEVE fazer é usar unwrap() cegamente em opções. Isso resultará em um panic no seu código, o que é exatamente o tipo de coisa que Rust foi projetado para prevenir! Em vez disso, você deve sempre tratar explicitamente todos os seus diferentes casos lógicos, e se deixar que Rust faça seu trabalho, seu código será super seguro.

No contexto do que estamos projetando para o módulo de saldos, temos um mapa que possui um número arbitrário de chaves de usuário e seus valores de saldo.

O que devemos fazer quando lemos o saldo de um usuário que não existe no nosso mapa?

Bem, o truque aqui é que no contexto das blockchains, um usuário ter None saldo e um usuário ter 0 saldo é a mesma coisa. Claro, há alguns detalhes mais finos a serem expressos entre um usuário que existe em nosso estado com valor 0 e um usuário que não existe de todo, mas para os propósitos de nossas APIs, podemos tratá-los da mesma forma.

Como isso se parece?

Bem, podemos usar unwrap_or(...) para tratar essa condição com segurança e tornar nossas futuras APIs mais ergonômicas de usar. Por exemplo:

use std::collections::BTreeMap;
 
let mut map = BTreeMap::new();
map.insert("alice", 100);
assert_eq!(*map.get(&"alice").unwrap_or(&0), 100);
assert_eq!(*map.get(&"bob").unwrap_or(&0), 0);

Como você pode ver, ao usar unwrap_or(&0) após ler do nosso mapa, somos capazes de transformar nosso Option em um inteiro básico, onde usuários com algum valor têm seu valor exposto e usuários com None são transformados em 0.

Vamos ver como isso pode ser usado a seguir.

Definindo e Lendo Saldos de Usuários Como você pode ver, nossa máquina de estados inicial começa com todos sem saldo.

Para tornar nosso módulo útil, precisamos ter pelo menos algumas funções que nos permitam criar novos saldos para usuários e ler esses saldos.

Crie uma nova função dentro de impl Pallet chamada fn set_balance:

impl Pallet {
    pub fn set_balance(&mut self, who: &String, amount: u128) {
        self.balances.insert(who.clone(), amount);
    }
 
    // -- snip --
}

Como você pode ver, esta função simplesmente recebe informações sobre qual usuário queremos definir o saldo e qual saldo queremos definir. Isso então empurra essa informação para nosso BTreeMap, e isso é tudo.

Crie uma nova função dentro de impl Pallet chamada fn balance:

impl Pallet {
    pub fn balance(&self, who: &String) -> u128 {
        *self.balances.get(&who).unwrap_or(&0)
    }
}

Como você pode ver, esta função nos permite ler o saldo dos usuários em nosso mapa. A função permite que você insira algum usuário e nós retornaremos o saldo dele.

🚨 Alerta: Note que fazemos nosso pequeno truque aqui! Em vez de expor uma API que força o usuário a lidar com um > Option, somos capazes de fazer nossa API sempre retornar um u128 convertendo qualquer usuário com valor None em 0.