From f27104188572c3d5583a259cc221726a2fd1ccf7 Mon Sep 17 00:00:00 2001 From: Julio Biason Date: Mon, 23 Sep 2019 13:13:17 -0300 Subject: [PATCH] More things about Rust --- .../porque-voce-deve-aprender-rust/index.md | 217 +++++++++++++++++- 1 file changed, 210 insertions(+), 7 deletions(-) diff --git a/content/presentations/porque-voce-deve-aprender-rust/index.md b/content/presentations/porque-voce-deve-aprender-rust/index.md index 6953aaf..2e12a76 100644 --- a/content/presentations/porque-voce-deve-aprender-rust/index.md +++ b/content/presentations/porque-voce-deve-aprender-rust/index.md @@ -22,7 +22,7 @@ vários conceitos interessantes, eu acho que o título correto deve ser "Porque Você DEVE Aprender Rust". Mas antes de começar a falar sobre Rust, é interessante que vocês conheçam -quem está falando sobre aprender Rust: +quem está dizendo que vocês "devem aprender Rust": Eu comecei a programar a mais de 30 anos. Em todos esses anos, eu já programei em Basic (usando número de linhas e Basic estruturado, também conhecido como @@ -34,7 +34,7 @@ applets Flash sem usar o editor da Adobe), PHP (quem nunca?), JavaScript (quem nunca?), Python, Objective-C, Clojure, Java, Scala (que eu não desejo que nem meu pior inimigo tenha que programar nela) e Rust. E essas são as linguagens que eu já escrevi código e que rodou. Fora essas, eu ainda conheço Haskell, -Perl e Swift, mas nessas eu não programei nada ainda. +Perl, um pouco de Ruby e Swift, mas nessas eu não programei nada ainda. Mas por que eu comentei sobre isso? Porque o que eu vou comentar aqui é, basicamente, _minha opinião_, depois de conhecer essas linguagens todas. @@ -252,9 +252,9 @@ mensagens de erro do compilador. ## Motivo 4: O Borrow Checker -O Borrow Checker é o cara que faz o Rust ser diferente de outras linguagens. E -que também mudou como eu pensava sobre programação, apesar de todas as -linguagens listadas no começo desse post. +O Borrow Checker é a funcionalidade que faz o Rust ser diferente de outras +linguagens. E que também mudou como eu pensava sobre programação, apesar de +todas as linguagens listadas no começo desse post. Por exemplo, no seguinte código: @@ -265,7 +265,7 @@ let a = String::from("hello"); {% note() %} O que está sendo feito aqui é que está sendo criada uma string -- uma lista de caracteres que pode ser expandida, se necessário -- utilizando uma constante -que é fica (por ficar em uma página de código em memória) como valor inicial. +que é fixa (por ficar em uma página de código em memória) como valor inicial. Quem já mexeu com C: Isso é o mesmo que alocar uma região de memória e copiar o conteúdo de uma variável estática para a nova região alocada. @@ -286,7 +286,7 @@ considerada como um indicador de uma posição de memória, algo do tipo No caso, `a` (a nossa variável) é "dona" de uma região de memória, a 0x3f5cbf89, que tem o tamanho de 5 bytes, do tipo String. -E aí quando tu tenta fazer uma atribuição de variáveis como, por exemplo: +E aí você faz uma atribuição de variáveis como, por exemplo: ```rust fn main() { @@ -459,3 +459,206 @@ porque, em ambos os casos, temos threads compartilhando memória mutável, que, como vimos pelas regras do Borrow Checker, não é possível fazer em Rust. ## Motivo 5: Tipos Algébricos + +("Tipos Algébricos", no nosso caso, é só um nome bonito para parecer +inteligente ao invés de dizer "Enums".) + +Rust, assim como várias outras linguagens, tem enums: + +```rust +enum IpAddr { + V4, + V6 +} +``` + +{% note() %} +Quando eu estava estuando sobre isso, eu descobri que as opções de um enum são +chamadas "variantes". +{% end %} + +Mas além de ter enum, uma das coisas que Rust permite é que as opções do enum +carreguem um valor com elas. + +```rust +enum IpAddr { + V4(String), + V6(String), +} +``` + +Aqui temos um enum com duas opções, `V4` e `V6`; cada um dessas opções carrega +uma string junto. + +Mas como se usa isso? + +```rust +let home = IpAddr::V4(String::from("127.0.0.1")); +``` + +É bem parecido com a forma em que definimos os valores de enumerações em +outras linguagens, com a String como parâmetro. + +É importante notar que não é preciso que todas as opções tenham os mesmos +parâmetros, e é possível ter opções sem nenhum parâmetro ou mesmo mais de um. + +E, para acessar os elementos, usamos `match`: + +```rust +match home { + V4(address) => println!("IPv4 addr: {}", address), + V6(address) => println!("Ipv6 addr: {}", address), +} +``` + +`match` usa pattern matching para validar as opções. No caso, se `home` for +`V4` com um valor dentro, o valor é extraído e o `println!` com a string +`IPv4` é usada; se for `V6`, o outro `println!` é usado. + +A parte interessante é que se amanhã surgir o IPv8, e eu adicionar `V8` no meu +enum, o código vai parar de compilar. Por que? Porque o pattern matching tem +que ser exaustivo -- ou seja, tem que capturar todas as opções possíveis. Isso +é importante para coisas como, por exemplo: + +```rust +enum Option { + Some(T), + None +} +``` + +Esse enum é o substituto para coisas com `null`; lembre-se que, em Rust, todas +as variáveis devem apontar para uma região de memória e `null` não é um +posição de memória e, por isso, Rust não tem `null`s. Assim, uma função que +poderia retornar `null` tem que retornar um `Option` e, quando é tratado o +retorno da função, é preciso tratar o valor do retorno de sucesso (`Some`) _e_ +o valor com `null` (`None`); não é possível acessar o valor com o valor de +sucesso sem tetar o que acontece se não vier valor. + +E isso nos leva ao próximo motivo que é... + +## Error Control + +Antes de entrar na questão de como Rust faz o tratamento de erros, deixem me +mostrar alguns exemplos de tratamentos em outras linguagens: + +Em Python: + +```python +try: + something() +except Exception: + pass +``` + +Em Java: + +```java +try { + something(); +} catch (Exception ex) { + System.out.println(ex); +} +``` + +Ou em C: + +```c +FILE* f = fopen("someting.txt", "wb"); +fprintf(f, "Done!"); +fclose(f); +``` + +Qual o problema com esses três exemplos? + +O problema é que em nenhum deles a situação do erro foi realmente tratada -- a +versão em C é a pior delas, pois se o `fopen` falhar por algum motivo, ele +retorna um `null` e tentar fazer um `fprintf` em um `null` gera um +Segmentation Fault. + +{% note() %} +Eu já fiz todos esses, na minha vida. +{% end %} + +Desde a base das bibliotecas do Rust, as funções retornam esse enum: + +```rust +enum Result { + Ok(T), + Err(E), +} +``` + +Como isso ajuda em alguma coisa? Bom, se tudo retorna `Result`, isso significa +que a única forma que eu tenho para pegar o resultado do sucesso é lidar com o +caso do erro, porque o `match` não vai deixar que eu simplesmente ignore isso. + +No nosso caso do C, o correspondente seria: + +```rust +match File::create("something.txt") { + Ok(fp) => fp.write_all(b"Hello world"), + Err(err) => println!("Failure! {}", err), +} +``` + +Ou seja, a única forma que eu tenho de pegar o `fp` (file pointer) pra poder +escrever no arquivo é usando um match tratando o `Ok` (sucesso) e `Err` +(erro), e eu não tenho como pegar o `fp` sem fazer esse tratamento todo. + +A única coisa que faltou é que o write também pode falhar; então teríamos + +```rust +match File::create("something.txt") { + Ok(fp) => match fp.write_all(b"Hello world") { + Ok(_) => (), + Err(err) => println!("Can't write! {}", err), + } + Err(err) => println!("Failure! {}", err), +} +``` + +... e aqui já estamos ficando verbosos demais. Para facilitar um pouco a vida, +o enum `Result` do Rust tem uma função chamada `.unwrap()`, que faz o +seguinte: se o resultado do `Result` for `Ok`, já extrai o valor e retorna o +valor em si; se o resultado for `Err`, chama um `panic!`, que faz a aplicação +"capotar": + +```rust +let mut file = File::create("something.txt").unwrap(); +file.write(b"Hello world").unwrap(); +``` + +"Mas Júlio", você deve estar pensando, "isso não é diferente do segmentation +fault". Em matéria de resultado final, não; mas se vocês pensarem bem, o que +está sendo feito é que explicitamente eu estou colocando um "aqui a aplicação +pode explodir", e não que alguma coisa em tempo de execução vai derrubar a +aplicação. E a palavra chave aqui é "explicitamente"; alguém pode considerar +que não tratar o `null` do `fopen` também é uma forma de deixar um "aqui a +aplicação pode explodir", mas não foi o compilador que deixou isso acontecer; +sempre que pode, ele tentou me impedir de fazer burrada. + +Outra forma de lidar com `Result` é o operador `?`: esse operador só funciona +em funções que também tem um retorno do tipo `Result`; o que ela faz é que +caso a chamada de função retorne um `Err`, esse `Err` é passado como retorno +da função com o operador; se a função retornar um `Ok`, então o valor +encapsulado é retornado diretamente. Mais uma vez no nosso exemplo da escrita +em arquivo: + +```rust +let mut file = File::create("something.txt")?; +file.write(b"Hello world")?; +OK(()) +``` + +O que acontece é que agora essas três linhas _tem_ que estar dentro uma função +com um `Result`. + +"Ah, barbada", você pensa, "vou botar `?` em tudo e nunca lidar com o erro". +Bom, sim, é uma opção, mas existe uma função que não se pode ter `Result`: a +`main`. Assim, mais cedo ou mais tarde, o erro _vai_ ter que ser lidado. + +{% note() %} +Existe uma forma de fazer o `main` retornar `Result`, mas ele basicamente +serve para transformar o `Result` num código de erro -- ou 0 em sucesso. +{% end %}