Rust adota uma abordagem extremamente elegante para garantir que erros em tempo de execução sejam tratados, impedindo, inclusive, que o um programa compile caso não se atenda as expectativas do compilador.
Para demonstrar essa elegância, façamos um comparativo breve com uma linguagem bem conhecida: C#.
Qual é o problema com a forma como C# lida com erros em tempo de execução
Pense, por um momento, em um método em C# para recuperar um registro no banco de dados. Como seria a assinatura deste método? Algo assim?
public Customer GetById(int id) { // .. }
Pois bem. Considere:
- Qual seria o retorno dessa função caso o id especificado não esteja no banco de dados? null? Um exception?
- O que deveria ocorrer caso houvesse um problema de conexão com o banco? Essa função deveria lançar uma exception? Caso essa exception não seja tratada, tudo bem se seu programa cair?
- O que ocorre caso o runtime não consiga memória para alocar o objeto de retorno? OutOfMemoryException, certo? Você sabia que não há forma de seu programa se recuperar caso isso aconteça?
Como pode ver, temos três tipos de erros diferentes que podem ocorrer e nenhuma indicação explícita disso na assinatura do método. Essa falta de expressividade é uma das características que menos nos agrada na linguagem C#.
Rust não tem null nem exceptions
Rust rompe com a forma como programadores C# (e Java) estão acostumados a expressar erros. Não há null nem exceptions. Aliás, essa é uma característica recorrente em linguagens com forte influência funcional (caso de Rust).
Em Rust, sempre que um erro “tratável” acontecer ele poderá ser “retornado” pela função onde este erro ocorreu.
Aliás, falando em erros “tratáveis”, precisamos entender que Rust segrega erros em duas categorias:
- erros que o programa consegue tratar e continuar funcionando;
- erros que o programa não consegue tratar e que devem encerrar sua execução imediatamente.
Funções que podem retornar erros que o programa consegue tratar retornam uma enumeração Result<T, E>, onde T é o tipo do retorno em caso de sucesso e E é o tipo do retorno em caso de erro.
A mesma função que escrevemos em C#, em Rust poderia ter uma assinatura assim:
fn getCustomerById(id: i32) -> Result<Customer, String> { // .. }
Implicações:
- A assinatura dessa função indica claramente que um erro pode acontecer e, que, caso um erro ocorra uma string será retornada (provavelmente com uma mensagem explicando erro).
- Não há maneira de acessar o retorno da função sem implementar algum tratamento para erros potenciais.
- Caso um erro irrecuperável ocorra, o programa será encerrado sem que tenhamos que implementar qualquer tratamento. Afinal, esse tratamento serviria de nada.
Como usar o retorno de funções que podem gerar erros
O compilador de Rust gera warnings sempre que um Result<T, E> é retornado mas não é utilizado.
O uso padrão do retorno ocorre, geralmente, através de pattern matching (outra característica recorrente de linguagens com influência funcional).
Vejamos um exemplo:
use std::fs::File; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => { panic!("There was a problem opening the file: {:?}", error) }, }; }
No exemplo acima, tentamos ler um arquivo. A função File::open retorna Result. Em caso de erro, a chamada a macro panic! dispara um erro que forçará o encerramento da execução.
Como entender mais sobre erros em Rust?
Mais uma vez, o livro The Rust Programming Language (disponível on-line) é uma excelente referência e é leitura mais do que recomendada. No caso de tratamento de erros, em Rust, recomendamos a leitura do capítulo Error Handling.
Era isso… por enquanto
Neste post mostramos a elegância de Rust usando como referência outra linguagem que amamos: C#.
A forma como Rust lida com erros não é, de forma alguma, original. Outras linguagens com influência funcional seguem a mesma ideia. Entretanto, a consistência e a expressividade de Rust reforçadas por seu compilador, ajuda na escrita de programas com menos chances de erros em tempo de execução.
Gostou? Deixe sua opinião nos comentários.