Rust desde Cero: Instalación, Hola Mundo y Primeros Pasos
¿Por qué aprender Rust en 2026?
Rust lleva varios años consecutivos como el lenguaje más amado en las encuestas de Stack Overflow, y no es casualidad. Es un lenguaje de sistemas que te da el rendimiento de C/C++ pero con garantías de seguridad de memoria en tiempo de compilación — sin garbage collector.
Empresas como Microsoft, Google, Amazon, Discord, Cloudflare y Meta lo usan en producción. Linux lo adoptó como segundo lenguaje oficial del kernel. Y el ecosistema ya supera las 100,000 crates (paquetes) en crates.io.
Si vienes de Python, JavaScript o Java, Rust te va a desafiar con conceptos nuevos como ownership y borrowing. Pero una vez que los entiendes, escribes código que es rápido, seguro y predecible.
Fuente: Tim Mossholder — Unsplash
¿Para qué se usa Rust?
- CLIs y herramientas de terminal — ripgrep, fd, bat, delta, zoxide
- Servidores web — Actix Web, Axum, Rocket
- WebAssembly — compilar a Wasm para ejecutar en el navegador
- Sistemas embebidos — microcontroladores sin sistema operativo
- Infraestructura cloud — Firecracker (AWS Lambda), Deno, SWC, Turbopack
- Blockchain — Solana, Polkadot (Substrate)
Instalación de Rust
Rust se instala con rustup, el gestor oficial de toolchains. Un solo comando y tienes todo: el compilador (rustc), el gestor de paquetes (cargo), y la documentación offline.
Linux, macOS y WSL
1# Instalar rustup (incluye rustc + cargo)
2curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
3
4# Seguir las instrucciones — elegir opción 1 (default)
5# Después, recargar el PATH:
6source $HOME/.cargo/env
7
8# Verificar la instalación
9rustc --version
10cargo --version
Windows
En Windows tienes dos opciones:
- Recomendado: Descarga
rustup-init.exedesde rustup.rs y sigue el instalador - Con winget:
1# Windows — con winget
2winget install Rustlang.Rustup
3
4# Verificar
5rustc --version
6cargo --version
Actualizar Rust
Rust lanza una versión estable cada 6 semanas. Para actualizar:
1# Actualizar a la última versión
2rustup update
3
4# Ver qué toolchains tienes instalados
5rustup show
Tu Primer Programa: Hola Mundo
Vamos a lo clásico. Hay dos formas de hacerlo: manualmente con rustc o con Cargo (la forma recomendada).
Forma directa con rustc
1# Crear el archivo
2mkdir hola-rust && cd hola-rust
1// main.rs
2fn main() {
3 println!("¡Hola, mundo!");
4}
1# Compilar y ejecutar
2rustc main.rs
3./main # Linux/macOS
4# main.exe # Windows
Algunas cosas que notar:
fn main()— la función de entrada, igual que en Cprintln!— es una macro (por el!), no una función. Las macros se expanden en tiempo de compilación- Las líneas terminan en
; rustcgenera un binario nativo — no necesita runtime ni VM
Forma recomendada con Cargo
Cargo es el build system y gestor de paquetes de Rust. Es como npm para Node.js o pip + Poetry para Python. Siempre usa Cargo para proyectos reales:
1# Crear un nuevo proyecto
2cargo new hola-mundo
3cd hola-mundo
4
5# Estructura generada:
6# hola-mundo/
7# ├── Cargo.toml ← manifiesto del proyecto (como package.json)
8# └── src/
9# └── main.rs ← tu código
10
11# Compilar y ejecutar en un solo comando
12cargo run
Salida:
1 Compiling hola-mundo v0.1.0 (/home/user/hola-mundo)
2 Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
3 Running `target/debug/hola-mundo`
4¡Hola, mundo!
cargo run durante desarrollo (compila + ejecuta). Usa cargo build --release para generar el binario optimizado para producción.
Cargo.toml — El manifiesto
1[package]
2name = "hola-mundo"
3version = "0.1.0"
4edition = "2024"
5
6[dependencies]
7# Aquí agregas crates externos, por ejemplo:
8# serde = { version = "1.0", features = ["derive"] }
9# tokio = { version = "1", features = ["full"] }
Variables y Tipos de Datos
En Rust, las variables son inmutables por defecto. Esto es intencional — te obliga a ser explícito cuando algo puede cambiar.
1fn main() {
2 // Inmutable — no se puede reasignar
3 let nombre = "Ferris";
4 // nombre = "Otro"; // ❌ Error de compilación
5
6 // Mutable — necesitas 'mut' explícito
7 let mut contador = 0;
8 contador += 1; // ✅ OK
9 println!("Contador: {contador}");
10
11 // Tipo inferido vs explícito
12 let edad = 25; // i32 por defecto
13 let precio: f64 = 99.99; // tipo explícito
14 let activo: bool = true;
15 let inicial: char = 'R';
16
17 // Shadowing — redefinir una variable en el mismo scope
18 let x = 5;
19 let x = x + 1; // nuevo x = 6
20 let x = x * 2; // nuevo x = 12
21 println!("x = {x}");
22}
Tipos principales
| Tipo | Descripción | Ejemplo |
|---|---|---|
i8, i16, i32, i64, i128 | Enteros con signo | let x: i32 = -42; |
u8, u16, u32, u64, u128 | Enteros sin signo | let age: u8 = 25; |
f32, f64 | Punto flotante | let pi: f64 = 3.14159; |
bool | Booleano | let ok = true; |
char | Carácter Unicode (4 bytes) | let emoji = '🦀'; |
String | String dinámico (heap) | let s = String::from("hola"); |
&str | String slice (referencia) | let s: &str = "hola"; |
(T, U) | Tupla | let punto = (3.0, 4.0); |
[T; N] | Array de tamaño fijo | let nums = [1, 2, 3]; |
Vec<T> | Vector dinámico | let v = vec![1, 2, 3]; |
Funciones
Las funciones en Rust se declaran con fn. Los tipos de parámetros son obligatorios y el tipo de retorno se indica con ->:
1// Función simple
2fn saludar(nombre: &str) {
3 println!("¡Hola, {nombre}!");
4}
5
6// Función con retorno
7fn sumar(a: i32, b: i32) -> i32 {
8 a + b // Sin ; → es una expresión que se retorna
9}
10
11// Función con múltiples retornos (tupla)
12fn dividir(a: f64, b: f64) -> (f64, f64) {
13 let cociente = a / b;
14 let residuo = a % b;
15 (cociente, residuo)
16}
17
18fn main() {
19 saludar("Ferris");
20
21 let resultado = sumar(3, 7);
22 println!("3 + 7 = {resultado}");
23
24 let (cociente, residuo) = dividir(17.0, 5.0);
25 println!("17 / 5 = {cociente}, residuo = {residuo}");
26}
;) es el valor de retorno. Si pones ; al final, devuelve () (unit type, equivalente a void). Este es un error muy común al empezar.
Control de Flujo
Rust tiene if, loop, while, for, y el poderoso match:
1fn main() {
2 // if como expresión — devuelve un valor
3 let temperatura = 35;
4 let clima = if temperatura > 30 {
5 "caliente"
6 } else if temperatura > 15 {
7 "agradable"
8 } else {
9 "frío"
10 };
11 println!("El clima está {clima}");
12
13 // for — itera sobre rangos y colecciones
14 for i in 1..=5 {
15 println!("Número: {i}");
16 }
17
18 let frutas = vec!["manzana", "pera", "mango"];
19 for fruta in &frutas {
20 println!("Fruta: {fruta}");
21 }
22
23 // match — pattern matching exhaustivo
24 let codigo = 404;
25 let mensaje = match codigo {
26 200 => "OK",
27 301 => "Movido permanentemente",
28 404 => "No encontrado",
29 500 => "Error del servidor",
30 _ => "Código desconocido", // _ = catch-all
31 };
32 println!("{codigo}: {mensaje}");
33
34 // match con rangos
35 let nota = 85;
36 let calificacion = match nota {
37 90..=100 => "A",
38 80..=89 => "B",
39 70..=79 => "C",
40 60..=69 => "D",
41 _ => "F",
42 };
43 println!("Nota {nota} = {calificacion}");
44}
switch en otros lenguajes, match en Rust debe ser exhaustivo — tienes que cubrir todos los casos posibles. El compilador te lo exige. Esto elimina bugs de "se me olvidó manejar ese caso".
Structs y Enums
Rust no tiene clases, pero tiene structs (datos) e impl blocks (métodos). Los enums de Rust son mucho más poderosos que en otros lenguajes — pueden contener datos.
1// Definir un struct
2struct Usuario {
3 nombre: String,
4 email: String,
5 edad: u8,
6 activo: bool,
7}
8
9// Implementar métodos
10impl Usuario {
11 // Constructor (convención: se llama "new")
12 fn new(nombre: &str, email: &str, edad: u8) -> Self {
13 Self {
14 nombre: nombre.to_string(),
15 email: email.to_string(),
16 edad,
17 activo: true,
18 }
19 }
20
21 // Método que usa &self (referencia inmutable)
22 fn presentarse(&self) {
23 println!("Soy {} ({}), {} años", self.nombre, self.email, self.edad);
24 }
25
26 // Método que modifica — necesita &mut self
27 fn desactivar(&mut self) {
28 self.activo = false;
29 println!("{} ha sido desactivado", self.nombre);
30 }
31}
32
33// Enum con datos asociados
34enum Rol {
35 Admin,
36 Editor { permisos: Vec<String> },
37 Lector,
38}
39
40fn describir_rol(rol: &Rol) {
41 match rol {
42 Rol::Admin => println!("Acceso total"),
43 Rol::Editor { permisos } => {
44 println!("Editor con permisos: {}", permisos.join(", "));
45 }
46 Rol::Lector => println!("Solo lectura"),
47 }
48}
49
50fn main() {
51 let mut user = Usuario::new("Ana", "[email protected]", 28);
52 user.presentarse();
53 user.desactivar();
54
55 let rol = Rol::Editor {
56 permisos: vec!["escribir".to_string(), "publicar".to_string()],
57 };
58 describir_rol(&rol);
59}
Ownership: El Concepto Clave de Rust
Este es el concepto más importante de Rust y lo que lo hace único. El sistema de ownership garantiza seguridad de memoria sin garbage collector. Hay tres reglas:
- Cada valor tiene un dueño (owner)
- Solo puede haber un dueño a la vez
- Cuando el dueño sale del scope, el valor se libera automáticamente (drop)
1fn main() {
2 // ── Move (transferencia de ownership) ──
3 let s1 = String::from("hola");
4 let s2 = s1; // s1 se "mueve" a s2
5 // println!("{s1}"); // ❌ Error: s1 ya no es válido
6
7 println!("{s2}"); // ✅ s2 es el nuevo dueño
8
9 // ── Clone (copia profunda) ──
10 let s3 = String::from("mundo");
11 let s4 = s3.clone(); // Copia explícita
12 println!("{s3} y {s4}"); // ✅ Ambos válidos
13
14 // ── Borrowing (préstamo con &) ──
15 let mensaje = String::from("¡Hola Rust!");
16 imprimir(&mensaje); // Prestamos una referencia
17 println!("{mensaje}"); // ✅ mensaje sigue siendo válido
18
19 // ── Mutable borrowing ──
20 let mut texto = String::from("Hola");
21 agregar_mundo(&mut texto); // Préstamo mutable
22 println!("{texto}"); // "Hola, mundo!"
23}
24
25fn imprimir(s: &String) {
26 // s es una referencia — no es dueño del valor
27 println!("{s}");
28}
29
30fn agregar_mundo(s: &mut String) {
31 s.push_str(", mundo!");
32}
¿Por qué importa esto?
En C/C++ puedes tener use-after-free, double-free, dangling pointers y data races. En Python/Java el garbage collector se encarga, pero pagas con rendimiento y pausas impredecibles. Rust elimina ambos problemas: el compilador verifica la seguridad de memoria en tiempo de compilación, sin costo en runtime.
Manejo de Errores: Result y Option
Rust no tiene excepciones ni null. En su lugar usa dos enums poderosos: Result<T, E> y Option<T>:
1use std::fs;
2use std::num::ParseIntError;
3
4// Option — cuando un valor puede existir o no
5fn buscar_usuario(id: u32) -> Option<String> {
6 match id {
7 1 => Some("Ana García".to_string()),
8 2 => Some("Luis López".to_string()),
9 _ => None,
10 }
11}
12
13// Result — cuando una operación puede fallar
14fn parsear_edad(texto: &str) -> Result<u8, ParseIntError> {
15 texto.parse::<u8>()
16}
17
18fn main() {
19 // Manejar Option
20 match buscar_usuario(1) {
21 Some(nombre) => println!("Encontrado: {nombre}"),
22 None => println!("Usuario no encontrado"),
23 }
24
25 // if let — más conciso cuando solo te importa un caso
26 if let Some(nombre) = buscar_usuario(3) {
27 println!("Encontrado: {nombre}");
28 } else {
29 println!("No existe el usuario 3");
30 }
31
32 // Manejar Result
33 match parsear_edad("25") {
34 Ok(edad) => println!("Edad: {edad}"),
35 Err(e) => println!("Error al parsear: {e}"),
36 }
37
38 // unwrap_or — valor por defecto si falla
39 let edad = parsear_edad("abc").unwrap_or(0);
40 println!("Edad (con fallback): {edad}");
41
42 // El operador ? — propagar errores automáticamente
43 match leer_config() {
44 Ok(contenido) => println!("Config: {contenido}"),
45 Err(e) => println!("Error leyendo config: {e}"),
46 }
47}
48
49fn leer_config() -> Result<String, std::io::Error> {
50 let contenido = fs::read_to_string("config.toml")?; // ? propaga el error
51 Ok(contenido)
52}
? es tu mejor amigo. En vez de escribir match en cada operación que puede fallar, ? propaga el error automáticamente al llamador. Es limpio, seguro y elimina el boilerplate.
Comandos Esenciales de Cargo
Referencia rápida de los comandos que más vas a usar:
1# Crear proyecto nuevo
2cargo new mi-proyecto # binario (ejecutable)
3cargo new mi-lib --lib # librería
4
5# Compilar
6cargo build # modo debug (rápido de compilar)
7cargo build --release # modo release (optimizado)
8
9# Compilar y ejecutar
10cargo run
11cargo run --release
12
13# Tests
14cargo test
15cargo test nombre_del_test # ejecutar un test específico
16
17# Verificar que compila sin generar binario (más rápido)
18cargo check
19
20# Formatear código (como prettier)
21cargo fmt
22
23# Linter (como eslint)
24cargo clippy
25
26# Agregar dependencia
27cargo add serde # agrega al Cargo.toml
28cargo add tokio --features full
29
30# Documentación de tus crates
31cargo doc --open
32
33# Actualizar dependencias
34cargo update
| Comando | Equivalente en otros lenguajes |
|---|---|
cargo new | npm init / go mod init |
cargo build | go build / mvn compile |
cargo run | go run / python main.py |
cargo test | pytest / npm test |
cargo add | npm install / pip install |
cargo fmt | prettier / black |
cargo clippy | eslint / pylint |
Próximos Pasos
Ya tienes las bases de Rust: instalación, Cargo, variables, funciones, structs, enums, ownership y manejo de errores. Desde aquí, los caminos más comunes son:
- El Rust Book — doc.rust-lang.org/book — el recurso oficial y más completo, gratuito
- Rustlings — github.com/rust-lang/rustlings — ejercicios interactivos para practicar
- Rust by Example — doc.rust-lang.org/rust-by-example — aprende con código ejecutable
- Construir una CLI con
clap— el primer proyecto práctico ideal - Construir una API REST con
AxumoActix Web
rustup update.
Comments
Sign in to leave a comment
No comments yet. Be the first!