Rust é uma linguagem de programação multiparadigma compilada desenvolvida pela Mozilla. É projetada para ser "segura, concorrente e prática", mas diferente de outras linguagens seguras, Rust não usa coletor de lixo. Possui suporte nativo ao WebAssembly.

A linguagem apareceu como um projeto pessoal de Graydon Hoare, empregado da Mozilla. A organização começou a apoiar o projeto em 2009 e anunciou-o em 2010. No mesmo ano, os esforços mudaram do compilador original (escrito em OCaml) para um auto-hospedado feito em Rust. Conhecido por rustc, conseguiu compilar-se pela primeira vez em 2011 e utiliza o LLVM como back-end. Foi lançada pela primeira vez uma versão numerada pré-alfa em 2012. Rust 1.0, a primeira versão estável, foi lançada em 15 de maio de 2015.

Foi considerada pelo público a linguagem "mais amada" por nove anos consecutivos, de acordo com pesquisas conduzidas pelo site Stack Overflow de 2016 a 2024, e está entre as 25 linguagens mais populares, de acordo com pesquisas conduzidas pela RedMonk desde 2018.

Design Rust se baseia nos seguintes princípios: segurança sem coletor de lixo, concorrência sem disputa de dados e abstração sem overhead. Estes princípios fazem com que Rust seja rápida para ser usada em aplicações de baixo nível como o motor de renderização Servo e também prática para projetos de alto nível.

Através do uso de mônadas e referências, Rust consegue limitar o uso de ponteiros, reduzindo as chances de encontrar ponteiros nulos ou soltos, e dificultando falhas de segmentação. Rust gerencia memória e recursos automaticamente, sem necessitar de um coletor de lixo. A linguagem impede condição de corridas entre threads pois não é possível que duas threads possam modificar um mesmo valor ao mesmo tempo. Para que uma referência possa ser compartilhada entre várias threads, ela deve ser somente leitura. Existem diversas técnicas seguras de comunicação entre threads.

O princípio de abstração sem overhead vem do C++. Nas palavras de Bjarne Stroustrup: "Você não paga por aquilo que você não usa. E mais: aquilo que você usa, não conseguiria programar melhor à mão". Rust permite um alto grau de abstração através do sistema de traits, que são interfaces que podem ser implementadas separadamente da declaração de um tipo. Tipos genéricos são utilizados extensamente.

O projeto Rust usa o conceito de "canais de lançamento", semelhante ao Mozilla Firefox; são 3 canais: Nightly, Beta e Stable ("estável"). Diariamente é lançada uma nova versão Nightly, e a cada seis semanas a última versão desse canal é promovida para Beta, e só receberá atualizações para corrigir falhas sérias. Simultaneamente, a última versão Beta é promovida para Stable.

Eventualmente a sintaxe da linguagem evolui, e novas palavras-chave podem ser adicionadas. Para evitar a quebra de compatibilidade com códigos antigos, novas edições são lançadas, que projetos antigos podem ativar opcionalmente; a última edição foi a 2021. Também é possível usar palavras-chave como identificadores, usando a seguinte sintaxe:

// `match` é uma palavra-chave fn r#match(needle: &str, haystack: &str) -> bool { haystack.contains(needle) }

Enumerações e casamento de padrões Rust possui enumerações de tipagem forte, e suas variantes podem carregar valores diversos. Casamento de padrões é muito importante em Rust, pois as enumerações são a base do tratamento de erros. Exemplo de declaração de enumerações e o casamento de padrões:

enum Browser { Firefox, Epiphany, Safari, Chrome, Edge, Ie(u8), }

let browser = Browser::Ie(11); match browser { Browser::Firefox => { println!("Gecko");

Browser::Epiphany | Browser::Safari => { println!("WebKit");

Browser::Ie(version) => { println!("Internet Explorer {version}");

_ => println!("Chromium/Blink"), // Todos os demais }

O comando match pode retornar valores e também pode ser usado com outros tipos. Exemplo:

let age = 27u8; let category = match age { // `..=4` é um padrão inclusivo (inclui 0, 1, 2, 3 e 4) ..=4 => "bebê", 5..=13 => "criança", 14..=17 => "adolescente", 18.. => "adulto", }; println!("Com {age} ano(s) você é considerado {category}.");

Padrões irrefutáveis (que não podem falhar), como tuplas e estruturas, podem ser desestruturados de forma mais simples. Exemplo:

fn print_addr(addr: (&str, &str)) { let (protocol, domain) = addr; // Tupla desestruturada em duas variáveis

println!("Protocolo: {protocol:?}\nDomínio: {domain:?}"); }

// Forma alternativa: desestruturação de parâmetro fn print_addr((protocol, domain): (&str, &str)) { println!("Protocolo: {protocol:?}\nDomínio: {domain:?}"); }

Inexistência de valor Rust usa enumerações para representar a inexistência de um valor na forma de enum Option<T> { None, Some(T) }. Exemplo:

let website = Some("https://www.wikipedia.org"); // `website: Option<&str>` match website { None => println!("website não especificado"), Some(addr) => println!("website: {addr}"), }

// Forma alternativa: if-let if let Some(addr) = website { // Pode usar `addr` apenas neste escopo println!("website: {addr}"); }

// Forma alternativa: let-else let Some(addr) = website else { panic!("website não especificado"); // Um `return` ou `break` também é aceitável aqui }; // Pode usar `addr` neste escopo...

Existem métodos na biblioteca padrão que simplificam esse tipo de tratamento. Alguns exemplos:

let url1 = None::<&str>; // `url1: Option<&str>` let url2 = None::<String>;

// Fornece um valor reserva let website = url1.unwrap_or("http://www.example.com");

// Fornece o valor padrão do tipo (string vazia) let website = url1.unwrap_or_default();

// Constrói um valor reserva através de uma clausura let domain = "www.example.com"; let website = url2.unwrap_or_else(|| format!("http://{domain}"));

// Aborta (pânico) let website = url1.unwrap();

// Aborta com uma explicação let website = url1.expect("endereço inexistente");

Semelhante ao operador ?. em outras linguagens, também existem métodos que permitem trabalhar com o possível valor de forma segura. Alguns exemplos:

let website = Some("https://www.wikipedia.org");

// Transforma o valor, se houver algum let len = website.map(|d| d.len()); // `len: Option<usize>`

// Trabalha com o valor, se houver algum. A clausura precisa retornar Option let domain = website.and_then(|addr| addr.strip_prefix("https://")); // `domain: Option<&str>`

Tratamento de erros Rust não possui exceções como em C++. Ao invés disso, possui duas formas de representar erros; a primeira é a macro panic! que indica defeitos de um programa, como divisões por zero, e causam o encerramento do programa. Exemplo:

for i in -5..5 { println!("{}", 10 / i); // Pânico: divisão por zero }

panic!("isto não deveria acontecer!"); // Alerta "pânico" manualmente

Em alguns casos óbvios de "pânico", como na divisão por zero, o compilador pode se recusar a compilar o código. Rust permite recuperar de alguns "pânicos" com a função especial std::panic::catch_unwind, que não deve ser usada para emular o tratamento de exceções de C++.

A segunda forma é criando uma instância de enum Result<T, E> { Ok(T), Err(E) }, que pode ser retornada para indicar se uma operação foi um sucesso ou não, como a leitura de um arquivo no disco. Exemplo de um erro personalizado:

use std::error::Error; use std::fmt;

// Erros podem ser de qualquer tipo #[derive(Debug)] #[non_exhaustive] enum ValueError { Empty, TooSmall, TooLarge, }

// Descrição do erro impl fmt::Display for ValueError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use ValueError::*; match self { Empty => "vazio", TooSmall => "número muito pequeno", TooLarge => "número muito grande",

.fmt(f)

// Métodos para extrair backtraces, entre outras coisas impl Error for ValueError {}

// Função que pode falhar fn verify(value: Option<i64>) -> Result<i64, ValueError> { let Some(n) = value else { return Err(ValueError::Empty);

match n { ..=3 => Err(ValueError::TooSmall), 21.. => Err(ValueError::TooLarge), _ => Ok(n),

E o tratamento do erro:

fn main() { let x = Some(3); let y = Some(4);

match verify(x) { Ok(n) => println!("Número: {n}"), Err(err) => eprintln!("Erro: {err}"),

match verify(y) { Ok(n) => println!("Número: {n}"), Err(err) => eprintln!("Erro: {err}"),

É possível simplificar o código retornando prematuramente os erros com o operador ?:

use std::error::Error;

// `Box<dyn Error>` representa qualquer tipo que implemente `Error` type AnyError = Box<dyn Error>;

fn main() -> Result<(), AnyError> { let x = Some(3); let y = Some(4);

println!("Número: {n}", n = verify(x)?); println!("Número: {n}", n = verify(y)?); // A segunda chamada só ocorrerá se a primeira não falhar Ok(()) // Retorna "sucesso" }

Genéricos e lifetimes Rust possui suporte a programação genérica e o conceito de lifetimes. Todas as referências possuem um tempo de vida, que deve ser anotado no código e é verificado em tempo de compilação; em alguns casos o compilador consegue inferir e a anotação se torna opcional. Todo objeto tem um dono (ownership), que pode emprestar (borrowing) ou mover (move). Ao mover a posse para outra variável, a anterior deixa de existir. Exemplo de uma árvore binária em Rust:

// `'a` é um lifetime, e `T` é um tipo genérico #[derive(Debug)] enum BinaryTree<'a, T: Copy> { Empty, Node { value: T, // Essas referências serão válidas enquanto `self` também for. // Ou seja, é impossível criar referências circulares. left: &'a BinaryTree<'a, T>, right: &'a BinaryTree<'a, T>, }, }

use BinaryTree::{Empty, Node}; let tree = Node { value: 5.96, left: &Empty, right: &Node { value: 1.0, left: &Empty, right: &Empty, },

// Move `left`, empresta `right` e ignora `value` if let Node { left, ref right, .. } = tree { println!("left: {left:#?},"); println!("right: {right:#?},"); }

Rust é mais restrita que outras linguagens; dado um objeto T, é apenas possível ter um dos seguintes casos:

Ter inúmeras referências imutáveis (&T) para o objeto (também conhecido como aliasing) Ter uma referência mutável &mut T para o objeto (também conhecido como mutabilidade)

Essa regra é aplicada pelo compilador. Em certas situações, essa regra pode não ser flexível o suficiente, como no caso de referências circulares, e para isso deve-se aplicar essa regra em tempo de execução usando algumas das várias estruturas de contagem de referências (std::rc::Rc, std::rc::Weak, etc) e mutabilidade interior (std::cell::Cell, std::cell::RefCell, std::cell::OnceCell, etc). Outra possibilidade é o uso de alocação de arena. Exemplo de uma árvore binária usando essas estruturas:

use std::cell::{Cell, RefCell}; use std::rc::{Rc, Weak};

// Apelido para simplificar type WeakRef<T> = Weak<RefCell<T>>;

#[derive(Debug)] struct BinaryTree<T: Copy> { value: Cell<T>, left: WeakRef<BinaryTree<T>>, right: WeakRef<BinaryTree<T>>, }

let tree = Rc::new(RefCell::new(BinaryTree { value: Cell::new(5.96), left: Weak::new(), // Referência fraca (inválida) right: Weak::new(), }));

let _tree_ref = Rc::clone(&tree); // Referência forte

{ // Empresta mutavelmente. Se já houver um empréstimo, causa pânico let mut node = tree.borrow_mut();

// Aponta a subárvore esquerda para a raiz da árvore, criando uma // referência circular. `Rc::downgrade()` transforma `Rc` em `Weak`. node.left = Rc::downgrade(&tree);

// `Cell` permite modificar o valor interior node.value.set(1.0); } // Devolve `tree` no fim do escopo

let node = tree.borrow(); // Empresta imutavelmente

// Verifica se a referência fraca é válida let left = node.left.upgrade(); let right = node.right.upgrade();

println!("left: {left:#?},"); println!("right: {right:#?},");

Se houver ambiguidade na invocação de um método genérico, o tipo pode ser explicitado com o símbolo turbofish (::<>). Exemplo:

let dollar = "5.96" .parse::<f64>() .expect("falha ao converter para `f64`"); println!("US$ 1.00 = R$ {dollar}");

Também é possível passar valores constantes como parâmetros genéricos. Exemplo: