Match

Em Rust não temos a estrutura de decisão switch, em seu lugar temos a expressão match, o seu comportamento é parecido, porém, com alguns recursos a mais. Vamos retomar aquele código dos enums.

#[derive(Debug)]
enum Uf {
    Sp(String),
    Rj(String),
    Ce(String),
}

impl Uf {
    fn retorna_sp() -> Self {
        Self::Sp(String::from("São Paulo"))
    }

    fn quem_sou_eu(&self) {
        todo!()
    }
}

fn main() {
    let uf = Uf::retorna_sp();
    println!("{:?}", uf);
    println!("{:#?}", uf);
}

Ficamos de implementar o método quem_sou_eu de propósito, nele iremos utilizar a estrutura de decisão match.

#[derive(Debug)]
enum Uf {
    Sp(String),
    Rj(String),
    Ce(String),
}
//--declaração do enum
impl Uf {
    fn retorna_sp() -> Self {
        Self::Sp(String::from("São Paulo"))
    }
    //--outros métodos
    fn quem_sou_eu(&self) {
        match self {
            Uf::Sp(_) => println!("Eu sou São Paulo"),
        }
    }
}

fn main() {
    let uf = Uf::retorna_sp();
    uf.quem_sou_eu();
}

Caso tentarmos compilar o código, teremos o seguinte erro:

error[E0004]: non-exhaustive patterns: `&Rj(_)` and `&Ce(_)` not covered
  --> main.rs:14:15
   |
2  | / enum Uf {
3  | |     Sp(String),
4  | |     Rj(String),
   | |     -- not covered
5  | |     Ce(String),
   | |     -- not covered
6  | | }
   | |_- `Uf` defined here
...
14 |           match self {
   |                 ^^^^ patterns `&Rj(_)` and `&Ce(_)` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
   = note: the matched value is of type `&Uf`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0004`.

O porquê deste erro?

Rust nos obriga a cobrir todos os casos que podem acontecer. Para isso vamos fazer a seguinte alteração.

#[derive(Debug)]
enum Uf {
    Sp(String),
    Rj(String),
    Ce(String),
}
//--declaração do enum
impl Uf {
    fn retorna_sp() -> Self {
        Self::Sp(String::from("São Paulo"))
    }
    //--outros métodos
    fn quem_sou_eu(&self) {
        match self {
            Uf::Sp(_) => println!("Eu sou São Paulo"),
            Uf::Rj(_) => println!("Eu sou Rio de Janeiro"),
            Uf::Ce(_) => println!("Eu sou Ceará"),
        }
    }
}

fn main() {
    let uf = Uf::retorna_sp();
    uf.quem_sou_eu();
}

Agora nosso código ira rodar, mas reparem que eu posso criar uma instância de enum com um valor e o código se repete nos 3 casos. Eu posso melhorar isso um pouco, realizando a troca do '_' por um nome, e também posso deixar todos no mesmo 'match' utilizando um pipe '|'.

#[derive(Debug)]
enum Uf {
    Sp(String),
    Rj(String),
    Ce(String),
}
//--declaração do enum
impl Uf {
    fn retorna_sp() -> Self {
        Self::Sp(String::from("São Paulo"))
    }
    //--outros métodos
    fn quem_sou_eu(&self) {
        match self {
            Uf::Sp(nome) | Uf::Rj(nome) | Uf::Ce(nome) => println!("Eu sou {}", nome),
        }
    }
}

fn main() {
    let uf = Uf::retorna_sp();
    uf.quem_sou_eu();
}

E temos o mesmo resultado. Porém se tivermos várias e várias opções e eu não precisar capturar os valores e terem a mesma tratativa. Porém, caso quisermos utilizar o mesmo modo acima e tivermos enum com quantidade de valores diferentes ou incompatíveis precisamos ignorar todos os valores não correspondentes para isso usamos o '_'.

enum Repositorio {
    Este(String, u16),
    Outros(String, u16, u8),
}

fn main() {
    let url_rust4noobs = String::from("https://github.com/pgjbz/rust4noobs");
    let qtd_stars_atuais_rust4noobs: u16 = 22;
    let url_4noobs = String::from("https://github.com/he4rt/4noobs");
    let qtd_stars_atuais_4noobs: u16 = 1964;
    let qualquer_numero_so_para_diferenciar: u8 = 20;

    let rust4noobs = Repositorio::Este(url_rust4noobs, qtd_stars_atuais_rust4noobs);
    let _4noobs = Repositorio::Outros(url_4noobs, qtd_stars_atuais_4noobs, qualquer_numero_so_para_diferenciar);

    match rust4noobs {
        Repositorio::Este(url, stars) | Repositorio::Outros(url, stars, _) => println!("Repositorio {}, estrelas {}", url, stars),
    }
}

Claro podemos quebrar em mais 'match' para não perder essas informações.

enum Repositorio {
    Este(String, u16),
    Outros(String, u16, u8),
}

fn main() {
    let url_rust4noobs = String::from("https://github.com/pgjbz/rust4noobs");
    let qtd_stars_atuais_rust4noobs: u16 = 22;
    let url_4noobs = String::from("https://github.com/he4rt/4noobs");
    let qtd_stars_atuais_4noobs: u16 = 1964;
    let qualquer_numero_so_para_diferenciar: u8 = 20;

    let rust4noobs = Repositorio::Este(url_rust4noobs, qtd_stars_atuais_rust4noobs);
    let _4noobs = Repositorio::Outros(url_4noobs, qtd_stars_atuais_4noobs, qualquer_numero_so_para_diferenciar);

    match rust4noobs {
        Repositorio::Este(url, stars) => println!("Repositorio {}, estrelas {}", url, stars),
        Repositorio::Outros(url, stars, n) => println!("Repositorio {}, estrelas {}, numero aleatório para diferenciar {}", url, stars, n),
    }
}

E se eu tenho, por exemplo um u8 eu preciso realizar um 'match' para cada possibilidade? Não eu posso fazer um 'match' com o '' novamente, seria como o 'default' do switch, mas lembre-se quando usamos o '' ignoramos o valor.

fn main() {
    let numero: u8 = 10;
    match numero {
        1 => println!("Um"),
        2 => println!("Dois"),
        3 => println!("Três"),
        _ => println!("Qualquer outro numero")
    }
}

Com o código acima cobrimos todas as possibilidades possíveis e qualquer valor que não seja 1, 2 ou 3 teremos a mesma tratativa.

A expressão match também pode ser utilizada comparar por uma faixa de valor. Utilizando a o 'match' da seguinte maneira 'inicio..=fim'

fn main() {
    let numero = 20;
    match numero {
        0..=9 => println!("Menor que 10"),
        _ => println!("Igual ou maior que 10")
    }
}

Ou podemos também utilizar o match para retornar algum valor de qualquer tipo. Mas todos os pontos de retornos tem que ser do mesmo tipo, ou algum tipo de expressão como continue, return, vamos usar o mesmo exemplo anterior, contudo retornando um boolean e armazenando em uma variável.

fn main() {
    let numero = 20;
    let menor_que_10 = match numero {
        0..=9 => true,
        _ => false
    };
    if menor_que_10 {
        println!("Menor que 10");
    } else {
        println!("Igual ou maior que 10");
    }
}

Match é uma estrutura bem poderosa, e seu uso é relativamente simples por enquanto