Browse Source

More things about Rust

master
Julio Biason 5 years ago
parent
commit
f271041885
  1. 217
      content/presentations/porque-voce-deve-aprender-rust/index.md

217
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<T> {
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<T, E> {
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 %}

Loading…
Cancel
Save