Rust para Desenvolvedores JavaScript: Guia Prático para Suas Primeiras 1000 Linhas (2026)
Você ouve isso em todo lugar: "Rust é o futuro." O Stack Overflow chama de linguagem mais admirada pelo 8º ano consecutivo. O Discord reconstruiu sua infraestrutura nela. A Cloudflare roda serviços críticos de edge com ela. O engine multiplayer do Figma? Rust. Até a Microsoft está reescrevendo componentes core do Windows em Rust.
Mas tem um problema—se você é desenvolvedor JavaScript ou TypeScript, todo tutorial de Rust parece ter sido escrito pra alguém com background em C++. Ownership? Borrowing? Lifetimes? A terminologia sozinha já é suficiente pra você fechar a aba e voltar pro seu confortável npm install.
Este guia é diferente. Vamos aprender Rust através da perspectiva de um desenvolvedor JavaScript. No final, você terá escrito suas primeiras 1000 linhas de Rust e realmente entenderá o que está acontecendo por baixo dos panos.
Por que um dev web ia querer aprender Rust?
Antes de codar, bora ser direto: Por que quem faz web ia aprender uma linguagem de sistemas?
A Realidade de Performance
JavaScript é interpretado (ou compilado JIT). Rust compila pra código de máquina nativo. A diferença não é sutil:
// JavaScript: Parsear JSON, encontrar valor máximo const data = JSON.parse(hugeJsonString); const max = Math.max(...data.numbers); // Tempo de execução: ~450ms para 10 milhões de números
// Rust: Mesma operação let data: Data = serde_json::from_str(&huge_json_string)?; let max = data.numbers.iter().max(); // Tempo de execução: ~12ms para 10 milhões de números
Não é erro de digitação—37x mais rápido para a mesma operação lógica. E isso importa quando você está:
- Construindo ferramentas CLI que precisam parecer instantâneas
- Processando arquivos grandes (ferramentas de build, linters)
- Escrevendo funções serverless onde tempo de cold start = dinheiro
- Criando módulos WebAssembly para tarefas pesadas no navegador
A Ponte WebAssembly
Aqui é onde fica interessante pra desenvolvedores web. Rust compila pra WebAssembly (WASM) melhor que qualquer outra linguagem:
// Este código Rust... #[wasm_bindgen] pub fn fibonacci(n: u32) -> u32 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2) } }
...vira um arquivo .wasm que você pode importar diretamente no JavaScript:
import init, { fibonacci } from './pkg/my_rust_lib.js'; await init(); console.log(fibonacci(40)); // Roda 10-20x mais rápido que JS puro
Empresas como Figma, Photoshop (versão web), e Google Earth usam exatamente esse padrão pra código crítico de performance.
Seu Primeiro Programa Rust: Comparando com JavaScript
Vamos começar com algo familiar. Aqui está um programa simples nas duas linguagens:
JavaScript:
function greet(name) { const message = `Hello, ${name}!`; console.log(message); } greet("World");
Rust:
fn greet(name: &str) { let message = format!("Hello, {}!", name); println!("{}", message); } fn main() { greet("World"); }
Já dá pra ver algumas diferenças:
fnao invés defunction- Tipos são explícitos:
name: &str letfunciona similar, masconstnão existe da mesma formaformat!eprintln!têm!porque são macros- Todo programa Rust precisa de uma função
main
Configurando Seu Ambiente
Antes de continuar, vamos instalar Rust:
# Instalar Rust (funciona no macOS, Linux, WSL) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Verificar instalação rustc --version cargo --version
cargo é o gerenciador de pacotes e ferramenta de build do Rust—pense nele como npm + webpack combinados.
# Criar novo projeto (tipo npm init) cargo new my-first-rust-app cd my-first-rust-app # Rodar o projeto cargo run
Os 3 conceitos principais: Ownership, Borrowing e Lifetimes
É aqui que devs JS costumam travar. Mas relaxa, vou explicar com analogias que você vai entender.
Problema: A Gestão de Memória Escondida do JavaScript
Em JavaScript, você nunca pensa em memória:
function processData() { const data = [1, 2, 3, 4, 5]; // Memória alocada const doubled = data.map(x => x * 2); // Mais memória alocada return doubled; } // Memória... eventualmente coletada pelo GC // Você não tem controle sobre quando a memória é liberada // Isso pode causar pausas inesperadas de GC em código crítico
JavaScript usa garbage collection. É conveniente mas imprevisível. Rust te dá controle sem os perigos do gerenciamento manual de memória.
Ownership: Um Dono, Sempre
Em Rust, todo valor tem exatamente um dono:
fn main() { let s1 = String::from("hello"); // s1 é dono da string let s2 = s1; // Ownership MOVE pra s2 // println!("{}", s1); // ERRO! s1 não é mais dono de nada println!("{}", s2); // Funciona tranquilo }
Em termos de JavaScript, imagina se isso acontecesse:
// "Ownership" hipotético em JavaScript let s1 = "hello"; let s2 = s1; // Em Rust, isso invalidaria s1 console.log(s1); // Em Rust, isso seria um erro!
Por que Rust faz isso? Porque quando s2 sai do escopo, Rust sabe exatamente quando liberar a memória. Não precisa de garbage collector.
Borrowing: Referências Sem Ownership
Mas espera—e se você só quer usar um valor sem tomar ownership? Isso é borrowing:
fn calculate_length(s: &String) -> usize { s.len() } // s sai do escopo, mas não é dono da String, então nada acontece fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // EMPRESTAMOS s1 com & println!("O tamanho de '{}' é {}.", s1, len); // s1 ainda válido! }
Pense no & como dizer "só tô olhando, não tô levando."
Comparando com JavaScript:
// JavaScript passa objetos por referência de qualquer jeito function calculateLength(s) { return s.length; } const s1 = "hello"; const len = calculateLength(s1); console.log(`O tamanho de '${s1}' é ${len}.`); // Funciona igual
A diferença? Em Rust, o compilador garante que calculate_length não pode modificar ou manter s1. Em JavaScript, você só tem que confiar na função.
Borrowing Mutável: A Regra do Escritor Único
Em Rust, você pode ter:
- Várias referências imutáveis (
&T) - OU uma referência mutável (
&mut T)
Nunca as duas ao mesmo tempo.
fn main() { let mut s = String::from("hello"); let r1 = &s; // OK: borrow imutável let r2 = &s; // OK: outro borrow imutável // let r3 = &mut s; // ERRO! Não pode fazer borrow mutável enquanto tem borrow imutável println!("{} e {}", r1, r2); // r1 e r2 não são mais usados depois desse ponto let r3 = &mut s; // Agora funciona! r3.push_str(", world"); }
Por que isso importa: Essa regra previne data races em tempo de compilação. Em JavaScript, você provavelmente já encontrou bugs onde uma parte do seu código muta um objeto enquanto outra parte está lendo. Rust torna isso impossível.
Padrões do dia a dia: JS vs Rust
Bora traduzir código que você usa todo dia.
Arrays e Iteração
JavaScript:
const numbers = [1, 2, 3, 4, 5]; // Map const doubled = numbers.map(x => x * 2); // Filter const evens = numbers.filter(x => x % 2 === 0); // Reduce const sum = numbers.reduce((acc, x) => acc + x, 0); // Find const firstEven = numbers.find(x => x % 2 === 0);
Rust:
let numbers = vec![1, 2, 3, 4, 5]; // Map let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect(); // Filter let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect(); // Reduce (chamado fold em Rust) let sum: i32 = numbers.iter().fold(0, |acc, x| acc + x); // Ou simplesmente: let sum: i32 = numbers.iter().sum(); // Find let first_even = numbers.iter().find(|x| *x % 2 == 0);
Diferenças principais:
|x|é a sintaxe de closure do Rust (tipo arrow functions).iter()cria um iterador.collect()transforma um iterador de volta em uma coleção- Você precisa especificar tipos ou deixar Rust inferir
Valores Opcionais (tratando null)
JavaScript:
function findUser(id) { const user = database.get(id); if (user === null || user === undefined) { return "Anonymous"; } return user.name; } // Ou com optional chaining const name = user?.name ?? "Anonymous";
Rust:
fn find_user(id: u32) -> String { let user = database.get(id); match user { Some(u) => u.name.clone(), None => String::from("Anonymous"), } } // Ou mais conciso let name = user.map(|u| u.name.clone()).unwrap_or(String::from("Anonymous")); // Ou ainda mais simples com if let if let Some(user) = database.get(id) { println!("Encontrado: {}", user.name); }
Rust não tem null. Em vez disso, você usa Option<T>:
Some(value)= tem um valorNone= não tem valor
O compilador te obriga a tratar os dois casos. Chega de erros de "undefined is not an object"!
Tratamento de Erros
JavaScript:
async function fetchData(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return await response.json(); } catch (error) { console.error("Fetch falhou:", error); throw error; } }
Rust:
use reqwest; async fn fetch_data(url: &str) -> Result<serde_json::Value, reqwest::Error> { let response = reqwest::get(url).await?; let data = response.json().await?; Ok(data) } // Usando match fetch_data("https://api.example.com/data").await { Ok(data) => println!("Dados recebidos: {:?}", data), Err(e) => eprintln!("Fetch falhou: {}", e), }
O operador ? é o equivalente do Rust a "se isso falhar, retorna o erro imediatamente." É tipo propagação automática de try-catch.
Bora construir algo de verdade: Uma CLI pra JSON
Vamos fazer uma ferramenta CLI que um dev JS usaria—um formatador de JSON:
use std::env; use std::fs; use serde_json::{Value, to_string_pretty}; fn main() { // Pegar argumentos de linha de comando (tipo process.argv) let args: Vec<String> = env::args().collect(); if args.len() != 2 { eprintln!("Uso: {} <arquivo.json>", args[0]); std::process::exit(1); } let filename = &args[1]; // Ler arquivo (tipo fs.readFileSync) let contents = match fs::read_to_string(filename) { Ok(c) => c, Err(e) => { eprintln!("Erro lendo arquivo: {}", e); std::process::exit(1); } }; // Parsear JSON let parsed: Value = match serde_json::from_str(&contents) { Ok(v) => v, Err(e) => { eprintln!("JSON inválido: {}", e); std::process::exit(1); } }; // Pretty print match to_string_pretty(&parsed) { Ok(pretty) => println!("{}", pretty), Err(e) => eprintln!("Erro formatando: {}", e), }; }
Pra rodar:
# Adicionar dependência ao Cargo.toml # [dependencies] # serde_json = "1.0" cargo build --release ./target/release/json-formatter bagunçado.json
O binário compilado tem ~1MB e roda em milissegundos—compara com enviar Node.js junto com sua ferramenta CLI.
Rust Async: Não É Tão Diferente
JavaScript moderno é tudo sobre async/await. Rust também tem:
JavaScript:
async function fetchMultiple(urls) { const promises = urls.map(url => fetch(url).then(r => r.json())); const results = await Promise.all(promises); return results; }
Rust (com runtime tokio):
use futures::future::join_all; async fn fetch_multiple(urls: Vec<&str>) -> Vec<Result<String, reqwest::Error>> { let futures = urls.iter().map(|url| async move { let response = reqwest::get(*url).await?; response.text().await }); join_all(futures).await } #[tokio::main] async fn main() { let urls = vec![ "https://api.example.com/1", "https://api.example.com/2", ]; let results = fetch_multiple(urls).await; for result in results { match result { Ok(body) => println!("Recebido: {:.100}...", body), Err(e) => eprintln!("Erro: {}", e), } } }
A estrutura é impressionantemente similar! A diferença principal é que Rust precisa de um runtime async explícito (tokio é o mais popular).
O Ecossistema Rust pra Desenvolvedores Web
Aqui estão os crates (pacotes npm do Rust) que você mais vai usar:
Frameworks Web
- Axum - O novo padrão, criado pelo time do Tokio
- Actix Web - Testado em batalha, extremamente rápido
- Rocket - Amigável pro dev, ótima ergonomia
Serialização
- Serde - O padrão de facto pra JSON, YAML, TOML, etc.
- serde_json - JSON especificamente
Cliente HTTP
- Reqwest - Tipo axios pro Rust
Ferramentas CLI
- Clap - Parsing de argumentos (tipo commander.js)
- Indicatif - Barras de progresso
- Colored - Cores no terminal
WebAssembly
- wasm-bindgen - Interop JS/Rust
- wasm-pack - Build e publicação de pacotes WASM
Comparação de Performance: Números Reais
Vamos comparar uma carga de trabalho realista—processar um arquivo JSON de 100MB:
| Tarefa | Node.js | Rust | Melhoria |
|---|---|---|---|
| Parsear JSON | 2.3s | 0.18s | 12.7x |
| Encontrar emails (regex) | 4.1s | 0.31s | 13.2x |
| Transformar & serializar | 3.8s | 0.24s | 15.8x |
| Uso de memória | 890MB | 210MB | 4.2x menos |
Esses números importam quando você está:
- Construindo ferramentas de build (tipo esbuild, escrito em Go por razões similares)
- Processando logs ou datasets grandes
- Rodando em ambientes com memória limitada (serverless, edge)
Erros que todo dev JS comete no começo
1. Strings São Complicadas
let s1 = "hello"; // &str - um slice de string (emprestado) let s2 = String::from("hello"); // String - uma string owned // Você não pode fazer isso: // let s3: String = "hello"; // Erro! // Você precisa converter: let s3: String = "hello".to_string(); let s4: String = String::from("hello");
Regra geral: Use &str pra parâmetros de função, String quando você precisa ser dono dos dados.
2. Sem Exceções, Só Results
// Isso não compila - você deve tratar o Result let file = File::open("data.txt"); // Retorna Result<File, Error> // Você deve tratar let file = File::open("data.txt")?; // Propagar erro // ou let file = File::open("data.txt").unwrap(); // Panic se erro // ou let file = File::open("data.txt").expect("Falhou ao abrir arquivo"); // Panic com mensagem
3. Imutabilidade É o Padrão
let x = 5; // x = 6; // Erro! Variáveis são imutáveis por padrão let mut y = 5; y = 6; // Funciona!
Isso é o oposto do let do JavaScript (mutável) vs const (imutável).
4. Sem Coerção Implícita de Tipos
let x: i32 = 5; let y: i64 = 10; // let z = x + y; // Erro! Não dá pra somar i32 e i64 let z = x as i64 + y; // Deve converter explicitamente
Ok, e agora? O que estudar?
Aqui vai um roadmap realista:
Semana 1-2: Básico
- Complete os primeiros 8 capítulos do "The Rust Book" (grátis online)
- Escreva programas pequenos: FizzBuzz, leitor de arquivos, CLI simples
Semana 3-4: Aprofundando em Ownership
- Releia os capítulos de ownership
- Complete exercícios do Rustlings (prática interativa)
- Construa uma app CLI de TODO com persistência em arquivo
Mês 2: Desenvolvimento Web
- Construa uma REST API com Axum
- Conecte a um banco de dados (SQLx ou Diesel)
- Deploy numa plataforma cloud
Mês 3: WebAssembly
- Construa um módulo WASM
- Integre com uma app React/Vue/Svelte
- Compare performance com JavaScript puro
Conclusão: Rust vale a pena ou não?
Pra desenvolvedores JavaScript, Rust não é um substituto—é um complemento. Você ainda vai escrever suas apps web em TypeScript. Mas quando você precisar de:
- Performance máxima pra tarefas compute-heavy
- Latência previsível sem pausas de GC
- Binários pequenos pra CLI tools ou serverless
- WebAssembly pra performance no navegador
...Rust é a resposta.
A curva de aprendizado é real. Ownership e borrowing vão te confundir no começo. O compilador vai rejeitar seu código constantemente (mas as mensagens de erro são genuinamente úteis).
Mas quando clicar—e vai clicar—você vai ter um superpoder que a maioria dos desenvolvedores JavaScript não tem. Você vai entender como memória realmente funciona. Você vai escrever código mais seguro em qualquer linguagem. E você vai ter uma ferramenta que pode resolver problemas que JavaScript simplesmente não consegue.
Começa com algo pequeno. Um formatador JSON. Um renomeador de arquivos. Uma ferramenta CLI simples. Deixa o compilador te ensinar. E antes que você perceba, você vai estar escrevendo Rust que roda 20x mais rápido que seu JavaScript.
Bem-vindo ao Rust. O compilador é rígido, mas tá do seu lado.
Explore ferramentas relacionadas
Experimente estas ferramentas gratuitas do Pockit