diff --git a/content/presentations/porque-voce-deve-aprender-rust/index.md b/content/presentations/porque-voce-deve-aprender-rust/index.md index 9b7d55c..6953aaf 100644 --- a/content/presentations/porque-voce-deve-aprender-rust/index.md +++ b/content/presentations/porque-voce-deve-aprender-rust/index.md @@ -117,7 +117,7 @@ Quantum (de novo, em Rust, usando a base do Servo), a correção foi (conforme descrito por Niko Matsakis na abertura do Rust Latam 2019) "trivial". {% end %} -## Motivo 1 +## Motivo 1: Quem Usa, Gosta Em 2019, O [StackOverflow](https://stackoverflow.com/) fez uma enquete com os visitantes, perguntando, entre várias outras perguntas, qual linguagem eles @@ -165,7 +165,7 @@ acredito que botaria fogo em tudo e iria plantar batatas, porque, honestamente, significa que nós não aprendemos nada sobre linguagens de programação. -# Motivo 3: Compilador É Chato, Mas Amigável +## Motivo 3: Compilador É Chato, Mas Amigável Deixem-me mostrar um código Rust: @@ -181,10 +181,17 @@ Aqui temos nosso primeiro contato com a sintaxe de Rust: `fn` define funções; assim como C, `main` é a função que indica onde a execução começa; `let` deixa definir variáveis e, apesar de não mostrar nesse trecho, as variáveis são fortemente tipadas, mas eu não precisei colocar o tipo porque o compilador -consegue inferir o tipo sozinho; linhas terminam com `;`; `println!` tem uma -exclamação porque essa função é uma macro e a exclamação é colocada para -diferenciar de funções normais (no caso, o `println!` vai ser expandido pelo -compilador por um conjunto maior de comandos). +consegue inferir o tipo sozinho (com algumas raras exceções); linhas terminam +com `;`; `println!` tem uma exclamação porque essa função é uma macro e a +exclamação é colocada para diferenciar de funções normais (no caso, o +`println!` vai ser expandido pelo compilador por um conjunto maior de +comandos). + +{% note() %} +Quem já brincou com `#define`s em C deve saber que não existe nada que indique +o que foi digitado é uma função mesmo ou um `#define` que vai ser expandido em +várias outras funções; Rust não deixa isso acontecer. +{% end %} E esse código Rust não compila. @@ -243,4 +250,212 @@ como compiladores funcionam e hoje é um dos expoentes nas questões de mensagens de erro do compilador. {% end %} -# Motivo 4: O Borrow Checker +## 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. + +Por exemplo, no seguinte código: + +```rust +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. + +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. +{% end %} + +Quando vocês olham esse código, o que vocês pensam? + +Eu sempre li como "A variável `a` recebe o valor `hello`". + +Eu nunca pensei nisso como "A posição de memória apontada por `a` tem o valor +`hello`"; ou algo como `let 0x3f5cbf89 = "hello"`. + +Entretanto, é isso que o compilador do Rust faz: cada atribuição de variável é +considerada como um indicador de uma posição de memória, algo do tipo + +![Visão de memória pelo compilador do Rust](rust-memory.png) + +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: + +```rust +fn main() { + let a = String::from("hello"); + let _b = a; + println!("{}", a) +} +``` + +... tudo parece normal. + +Exceto que esse código não compila. + +``` +error[E0382]: borrow of moved value: `a` + --> src/main.rs:5:20 + | +4 | let _b = a; + | - value moved here +5 | println!("{}", a) + | ^ value borrowed here after move + | + = note: move occurs because `a` has type + `std::string::String`, which does not + implement the `Copy` trait +``` + +Por que? Porque a região de memória que `a` apontava (aquela que fica em +0x3f5cbf89, que tem 5 bytes e é do tipo String) agora pertence a `b`; `a` fica +apontando para... nada. E "nada", não é `null`: como todas as variáveis tem +que apontar para uma posição de memória, `a` se torna inválido e não pode mais +ser utilizado. + +"Mas e se eu precisar acessar uma posição de memória/valor em mais de um +lugar?" Bom, aí você pode usar referências, usando `&`: + +```rust +fn main() { + let a = String::from("hello"); + let _b = &a; + println!("{}", a) +} +``` + +Utilizar referências faz, basicamente, isso: + +![Visão de memória pelo compilador do Rust quando você usa referências](rust-reference.png) + +Existem várias regras que o Borrow Checker executa: + +* Uma região de memória tem apenas um dono; +* Passar um valor (região de memoria) de uma variável para outra troca o dono; +* A região é desalocada quando o dono sair de escopo; + +{% note() %} +Estas três regras estão interligadas: com a memória sendo desalocada quando a +variável sai de escopo, não precisamos mais nos preocupar em fazer `free()` +(apesar de que, agora, precisamos nos preocupar quanto tempo queremos que a +variável/região de memória permaneça alocada através dos nossos blocos de +código); tendo apenas um dono (e esse dono muda em caso de atribuição), +evita-se o problema de um "double `free()`". +{% end %} + +* Uma região de memória pode ter infinitas referências; +* ... desde que elas não durem mais que o dono da região original; + +{% note() %} +As referências não devem durar mais que a variável original para evitar que +elas continuem sendo utilizadas depois que o valor original foi feito +`free()`. +{% end %} + +* Assim como temos variáveis definidas como mutáveis, referências também podem + ser criadas como mutáveis; +* Não é possível criar referências mutáveis de variáveis imutáveis; +* Para haver uma referência mutável, é preciso que ela seja a _única_ + referência (mutável ou não). + +{% note() %} +Isso garante que não existam duas threads tentando escrever na mesma posição +de memória ao mesmo tempo. +{% end %} + +Ok, com todas essas regras, você deve estar se perguntando: E pra que serve +tudo isso? + +Duas respostas para essa pergunta: + +A primeira é o seguinte código (em Go, porque é mais fácil de explicar): + +```go +presente := Presente { ... } +canal <- presente +``` + +`presente` é uma estrutura qualquer que eu criei; `canal` é o canal de +comunicação entre duas threads; o que está sendo feito aqui é que uma thread +está criando uma estrutura e enviado para o outra thread. + +Nada demais; o problema está em fazer algo do tipo: + +```go +presente := Presente { ... } +canal <- presente +presente.abrir() +``` + +Se eu enviei o presente para outra pessoa, como foi que eu abri? Se eu mandei +uma estrutura para outra thread, como foi que o compilador deixou eu fazer uma +alteração nessa estrutura, se agora ela é da outra thread? + +A outra resposta é que chegamos ao limite do silício. Alguns podem não saber +disso, mas a pouco tempo havia uma ser "briga" entre donos de computadores pra +ver qual tinha o mais potente, e nós fazíamos isso contando vantagem com o +clock do processador: "O meu tem 3Ghz", "Ah, mas o meu tem 3Ghz _e meio_!" +Esse tipo de discussão sumiu, por um único motivo: não temos mais como fazer o +silício vibrar mais. A coisa ficou tão complexa que o que é feito agora é +colocar mais CPUs _dentro_ da CPU. + +Para tirar proveito da "CPUs dentro da CPU", precisamos de threads, e se o +compilador não proteger contra o uso inválido de memória entre as threads, nós +ainda vamos ter aqueles alertas de que a aplicação parou de funcionar as 4 da +manhã, e você vai tentar descobrir o que aconteceu e nada faz sentido. + +A ideia do Borrow Checker é tão boa que mais linguagens estão utilizando: +Swift 5 tem um controle chamado "Exclusitivy Enforcement" que é, basicamente, +um borrow checker mais light; Ada, uma das três linguagens aceitas pela MISRA +para software em que vidas humanas estão em jogo (controle de aviões, carros e +equipamentos médicos, por exemplo), ganhou um borrow checker na última versão +(pelo menos, "última" no momento em que esse post estava sendo escrito). + +## Intervalo: Anedotas + +Duas anedotas sobre a minha vida de programador: + +Numa época em que eu trabalhava num projeto gigantesco em C, eu estava +esperando minha carona para voltar pra casa enquanto uma das desenvolvedoras +estava brigando com o código. "Eu não consigo entender, " -- disse ela -- "eu +estou tentando ver o tempo que uma regra de negócio leva pra executar, mas +está dando que tá levando menos de 1 segundo, quando eu _sei_ que tem uma +pesquisa no banco que é demorada!" + +"Como é que tu tá pegando esse tempo de execução?" -- perguntei. + +"Eu faço um `localtime` no começo da execução e um `localtime` no final e vejo +a diferença." + +Nesse momento, me lembrei que `localtime` não é thread-safe: Quando o valor é +capturado, é passada uma região de memória a ser preenchida, mas cada vez que +o `localtime` é chamada, a mesma região é atualizada; o que estava acontecendo +é que as outras threads, que também estavam fazendo a chamada para `localtime` +estavam todas apontando para a mesma região de memória e todas elas estavam +mudando na mudança de valor. + +Em tempos mais recentes, estávamos trabalhando em Java e usando +`SimpleDateFormatter`. Em certos casos, começamos a receber alertas do tipo +"Data inválida: ''" ou "Data inválida: "R"". "Mas como? Tá aqui o JSON de +entrada e lá _tem_ valor!" + +Mais uma vez, acendeu a luzinha na minha cabeça e a primeira coisa que eu fiz +foi pesquisar "SimpleDateFormatter é thread-safe?" O primeiro resultado foi +um "Por que SimpleDateFormat não é thread-safe?" Trocamos pela versão mais +nova (outra classe) e tudo passou a funcionar normalmente. + +Aí vem a pergunta: Se usássemos Rust ao invés de C ou Java, isso resolveria +nossos problemas?" + +A resposta é "Sim!", porque o Rust sequer ia deixar o código compilar -- +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 diff --git a/content/presentations/porque-voce-deve-aprender-rust/rust-memory.png b/content/presentations/porque-voce-deve-aprender-rust/rust-memory.png new file mode 100644 index 0000000..1db55bc Binary files /dev/null and b/content/presentations/porque-voce-deve-aprender-rust/rust-memory.png differ diff --git a/content/presentations/porque-voce-deve-aprender-rust/rust-reference.png b/content/presentations/porque-voce-deve-aprender-rust/rust-reference.png new file mode 100644 index 0000000..394b801 Binary files /dev/null and b/content/presentations/porque-voce-deve-aprender-rust/rust-reference.png differ