Kauê Fraga

Desenvolvedor de Software

Funcionalidades do Rust

19 fev. 2024

Olá! Se você quer saber um pouco mais sobre as principais funcionalidades do Rust, então acho que posso ajudar. Bora 👊

A linguagem de programação Rust tem várias features peculiares e legais! Uma coleção de técnicas que deram certo em outras linguagens e paradigmas.

Features abordadas:

Zero-cost abstractions

“What you don’t use, you don’t pay for. What you do use, you couldn’t hand code any better”

Direto do C++.

As abstrações (pattern matching, iterators, generics, collections…) de recursos-base (for loops, if/else, raw pointers…) da linguagem não devem consumir nenhum recurso além daqueles já consumidos. Zero custo!

Ownership

Baseado no RAII (Resource Acquisition Is Initialization) design pattern do C++. Esse padrão de projeto diz: recursos como memória alocada, file handles e database connections devem estar presos a um tempo de vida. Quando um objeto é criado, os recursos são adquiridos e quando o objeto é destruído, os recursos são liberados. Simples. Esse padrão é tão útil porque liberar os recursos é algo facilmente esquecido, você pode ter escrito bastante código antes de precisar liberar e esqueceu, resultando em memory leaks.

Tão útil que… Rust integrou ele diretamente na linguagem e assim surgiu o Ownership. Para não precisar lembrar de usar esse padrão, o próprio compilador vai te forçar a seguir um conjunto de regras chamado de “ownership rules”.

Ownership Rules

  1. Cada valor em Rust tem uma variável que é sua dona, ou seja, cada valor tem um dono.
  2. Só pode existir um dono de uma vez.
  3. Quando um dono sai de escopo, o valor é largado ou dropped.

Essas regras são combinadas com as chamadas borrowing rules.

Borrowing Rules

  1. Em um momento qualquer, você pode ter uma referência mutável ou infinitas referências imutáveis.
  2. Referências DEVEM sempre ser válidas. SEM NULL/NIL NESSA PO@#$!!

Veja também exemplos em [[Rust Ownership]].

Algebraic Type System

Algebraic data types ou ADTs permitem a criação de tipos compostos usando sum types e product types. Geralmente implementado em linguagens funcionais, como Haskell.

Um product type é um tipo composto de dois ou mais tipos. Pense em um tipo usuário que tem nome e idade, outros dois tipos, dentro dele. Classes (OO) são um exemplo de product type. Basicamente, toda linguagem que tem um sistema de tipos, tem product types.

Um sum type é um tipo que pode ser representado por uma lista finita de opções válidas (como um enum).

Polymorphism

Direto do paradigma de orientação a objetos (OOP), polimorfismo é a capacidade que um objeto ou uma função têm de assumir múltiplas formas ou se comportar diferente dependendo do contexto em que é usada. O polimorfismo geralmente é implementado a partir da herança (inheritance). Já no Rust, o polimorfismo é implementado com traits e generics.

Traits definem um conjunto de funções/métodos que um tipo pode implementar, parecido com interfaces em outras linguagens. Lembre-se, tipos podem implementar múltiplos traits e traits podem ter implementações por padrão (default).

Generics te permitem escrever código abstrato em relação ao tipo, que te leva a código reutilizável e eficiente. Você pode dizer que um tipo necessário para uma função, não é algo específico, mas algo que implemente funções específicas.

O sistema de traits (trait system) em Rust tem sua raiz nas type classes do Haskell. Rust implementa o trait system juntamente do ownership/borrowing e lifetimes, que são um dos motivos da garantia de memory-safety. Alguns benefícios em relação a herança:

Async/await

Rust roubou a sintaxe async/await para programação assíncrona da linguagem de programação JavaScript. O paradigma assíncrono permite que tarefas sejam executadas independentemente e concorrentemente sem bloquear a execução do programa principal. O JavaScript usa Promises para representar o resultado de uma operação assíncrona e para utilizar esse resultado é necessário usar os métodos .then(), .catch() e .finally(). O problema é que esses métodos podem levar a um código aninhado e complexo. A sintaxe async/await no JavaScript permite que código assíncrono seja escrito da forma como código síncrono é escrito, sem usar then, catch e finally. No final é só um syntactic sugar para trabalhar com as Promises.

O Rust adotou a programação assíncrona de maneira parecida. Notação async antes de fn e .await? a cada chamada assíncrona. O ponto de interrogação serve para propagar erros. Como em JavaScript, async/await em Rust é um syntactic sugar para trabalhar com Futures (similar a Promises). Algumas diferenças do Rust para o JavaScript:

Meta programming

O Rust tem uma feature muito poderosa, os macros. Para entender os macros, vamos falar de meta programação.

Meta programação é uma técnica que permite um programa a manipular ou gerar código durante a compilação (compile time) ou execução (runtime). Macros são um recurso do Rust que permite o desenvolvedor a definir uma sintaxe customizada e fazer geração de código ou transformação de código. Eles permitem a meta programação com uma forma de escrever código que gera ou altera outro código durante a compilação (compile time).

Cargo

Inspirado no NPM (node package manager) do NodeJS, o Cargo é a ferramenta oficial de building e gerenciamento de pacotes do Rust. Com simples comandos no terminal é possível instalar as dependências de um projeto, compilar o projeto, testar o projeto, etc. O crates.io é onde ficam as caixas/crates, os pacotes do ecossistema Cargo. O package.json em JavaScript é o cargo.toml em Rust.

Obrigado por ler! ❤

Fontes: