Programación · n.º 12 entre los lenguajes más usados

Aprende Rust desde cero

Tan rápido como C++ pero sin fallos de memoria: el compilador te protege.

  • 10 lecciones
  • 10 ejercicios interactivos
  • 19 conceptos clave
  • 18 % lo usa*

Qué es Rust y para qué sirve

Rust se compila a código máquina como C y C++, pero su compilador comprueba las reglas de propiedad de la memoria y rechaza el código que podría provocar errores como usar memoria ya liberada o carreras de datos entre hilos. Por eso lo adoptan proyectos de sistemas, navegadores y herramientas de línea de comandos.

  • Programas de sistemas y herramientas de terminal
  • Servicios web rápidos
  • WebAssembly y dispositivos

Para empezar: Prueba los ejemplos en el Rust Playground oficial o instala Rust con rustup y crea un proyecto con cargo new.

Nivel 1 Básico

Lección 1 · Básico

Tu primer programa con Cargo

Cargo es la herramienta oficial de Rust: crea proyectos, descarga dependencias, compila y ejecuta. cargo new hola crea la carpeta con src/main.rs y cargo run lo compila y ejecuta.

println! lleva una exclamación porque es una macro, no una función. Las llaves {} dentro del texto se sustituyen por los valores que pases después.

Ejemplo práctico
// src/main.rs
fn main() {
    println!("¡Hola, mundo!");
    println!("2 + 3 = {}", 2 + 3);
}

// cargo new hola && cd hola && cargo run

Ejercicio

¿Por qué println! lleva exclamación?

Ver la solución

Respuesta: Porque es una macro. En Rust, el ! indica que se llama a una macro, que genera código al compilar.

Lección 2 · Básico

Variables inmutables y mut

En Rust las variables son inmutables por defecto: una vez asignadas no cambian. Si necesitas cambiarlas, decláralas con let mut.

El compilador deduce los tipos, pero puedes indicarlos: i32 (entero), f64 (decimal), bool, char y &str o String para texto.

Ejemplo práctico
fn main() {
    let nombre = "Ana";        // &str
    let mut edad: u32 = 30;
    edad += 1;

    let precio: f64 = 19.99;
    println!("{nombre} tiene {edad} años y paga {:.2}", precio * 3.0);

    // nombre = "Eva";  // error: cannot assign twice to immutable variable
}

Ejercicio

Completa para que la variable pueda cambiar.

let  contador = 0;
contador += 1;
Ver la solución

Respuesta: mut. Sin mut, el compilador rechaza cualquier reasignación.

Lección 3 · Básico

Control de flujo

if no lleva paréntesis y es una expresión: puede devolver un valor. for recorre rangos (1..=5 incluye el 5) y colecciones, while repite mientras se cumpla algo y loop repite hasta un break.

Las condiciones tienen que ser bool: Rust no convierte números en verdadero o falso.

Ejemplo práctico
fn main() {
    let nota = 7;
    let resultado = if nota >= 5 { "aprobado" } else { "suspenso" };
    println!("{resultado}");

    for i in 1..=3 {
        println!("vuelta {i}");
    }
}

Ejercicio

¿Qué números recorre for i in 1..=3?

Ver la solución

Respuesta: 1, 2 y 3. ..= incluye el último valor; 1..3 iría de 1 a 2.

Lección 4 · Básico

Vectores y texto

Vec<T> es una lista que crece; la macro vec! la crea con valores. Recorre sus elementos con for sobre una referencia (&notas) para no consumir el vector.

Hay dos tipos de texto: &str es una referencia a un texto que no es tuyo (como un literal) y String es un texto propio que puede crecer.

Ejemplo práctico
fn main() {
    let mut notas = vec![7, 8, 9];
    notas.push(10);
    let suma: i32 = notas.iter().sum();
    println!("Media: {}", suma as f64 / notas.len() as f64);   // 8.5

    let mut saludo = String::from("Hola");
    saludo.push_str(", Rust");
    println!("{saludo}");
}

Ejercicio

¿Qué tipo de texto puedes ampliar con push_str?

Ver la solución

Respuesta: String. String es dueño de su contenido y puede crecer; &str solo apunta a un texto existente.

Nivel 2 Intermedio

Lección 5 · Intermedio

Ownership y préstamos

Cada valor tiene un único dueño. Al asignar un String a otra variable o pasarlo a una función, la propiedad se mueve y la variable original ya no se puede usar.

Para usar un valor sin quedártelo, lo pides prestado con una referencia &. Puede haber muchas referencias de solo lectura o una sola &mut que modifica, pero no las dos a la vez: así Rust evita errores de memoria al compilar.

Ejemplo práctico
fn longitud(texto: &String) -> usize {   // préstamo de solo lectura
    texto.len()
}

fn anadir_punto(texto: &mut String) {    // préstamo para modificar
    texto.push('.');
}

fn main() {
    let mut frase = String::from("Hola");
    println!("{}", longitud(&frase));    // 4
    anadir_punto(&mut frase);
    println!("{frase}");                 // Hola.

    let otra = frase;                    // la propiedad se mueve
    // println!("{frase}");              // error: borrow of moved value
    println!("{otra}");
}

Ejercicio

Tras let otra = frase; (con frase un String), ¿qué pasa al usar frase?

Ver la solución

Respuesta: Error de compilación: el valor se ha movido. La propiedad pasó a otra; el compilador impide usar frase después.

Lección 6 · Intermedio

Structs e impl

Un struct agrupa datos con nombre. En un bloque impl escribes sus métodos: &self para leer, &mut self para modificar.

Una función asociada sin self, como new, se llama con Tipo::new() y suele servir de constructor.

Ejemplo práctico
struct Cuenta {
    titular: String,
    saldo: f64,
}

impl Cuenta {
    fn new(titular: &str) -> Self {
        Cuenta { titular: titular.to_string(), saldo: 0.0 }
    }
    fn ingresar(&mut self, cantidad: f64) {
        self.saldo += cantidad;
    }
}

fn main() {
    let mut c = Cuenta::new("Ana");
    c.ingresar(50.0);
    println!("{}: {}", c.titular, c.saldo);   // Ana: 50
}

Ejercicio

Completa el parámetro de un método que modifica el struct.

fn ingresar(& self, cantidad: f64) { … }
Ver la solución

Respuesta: mut. &mut self presta el objeto para modificarlo.

Lección 7 · Intermedio

Enums, match y Option

Un enum define un tipo que es una de varias variantes, y cada una puede llevar datos. match decide qué hacer en cada caso y el compilador exige que cubras todos.

Rust no tiene null: un valor que puede faltar es un Option<T>, que vale Some(valor) o None, y tienes que tratar los dos casos.

Ejemplo práctico
enum Pago {
    Efectivo,
    Tarjeta { ultimos4: String },
}

fn describir(p: &Pago) -> String {
    match p {
        Pago::Efectivo => "Pago en efectivo".to_string(),
        Pago::Tarjeta { ultimos4 } => format!("Tarjeta ****{ultimos4}"),
    }
}

fn main() {
    println!("{}", describir(&Pago::Tarjeta { ultimos4: "1234".into() }));
    let precios = vec![3.5, 12.0];
    match precios.first() {
        Some(p) => println!("Primero: {p}"),
        None => println!("Lista vacía"),
    }
}

Ejercicio

¿Cómo representa Rust un valor que puede faltar?

Ver la solución

Respuesta: Option<T>. Option<T> obliga a tratar el caso None, así que no hay errores por valores nulos olvidados.

Nivel 3 Avanzado

Lección 8 · Avanzado

Errores con Result y el operador ?

Las operaciones que pueden fallar devuelven Result<T, E>: Ok(valor) o Err(error). No hay excepciones; el tipo te obliga a decidir qué hacer con el error.

Dentro de una función que también devuelve Result, el operador ? devuelve el error hacia arriba automáticamente si algo falla.

Ejemplo práctico
use std::fs;
use std::num::ParseIntError;

fn leer_numero(texto: &str) -> Result<i32, ParseIntError> {
    let n: i32 = texto.trim().parse()?;   // si falla, devuelve el Err
    Ok(n * 2)
}

fn main() {
    println!("{:?}", leer_numero("21"));    // Ok(42)
    println!("{:?}", leer_numero("hola"));  // Err(ParseIntError { kind: InvalidDigit })

    match fs::read_to_string("notas.txt") {
        Ok(texto) => println!("{texto}"),
        Err(e) => println!("No se pudo leer: {e}"),
    }
}

Ejercicio

Completa el operador que propaga el error.

let n: i32 = texto.parse();
Ver la solución

Respuesta: ?. ? desempaqueta el Ok o devuelve el Err a quien llamó a la función.

Lección 9 · Avanzado

Traits y genéricos

Un trait define un comportamiento que varios tipos pueden implementar, parecido a una interfaz. Con genéricos y límites de trait (T: Display) escribes funciones que aceptan cualquier tipo que cumpla ese comportamiento.

Muchas capacidades básicas son traits de la biblioteca estándar: Display (mostrar), Clone (copiar) o Debug (mostrar para depurar, con {:?}).

Ejemplo práctico
use std::fmt::Display;

trait Area {
    fn area(&self) -> f64;
}

struct Cuadrado(f64);
impl Area for Cuadrado {
    fn area(&self) -> f64 { self.0 * self.0 }
}

fn mostrar<T: Display>(etiqueta: &str, valor: T) {
    println!("{etiqueta}: {valor}");
}

fn main() {
    mostrar("Área", Cuadrado(3.0).area());   // Área: 9
    mostrar("Nombre", "Ana");
}

Ejercicio

¿A qué se parece un trait en otros lenguajes?

Ver la solución

Respuesta: A una interfaz. Un trait declara métodos que cada tipo implementa a su manera, como una interfaz.

Lección 10 · Avanzado

Concurrencia sin miedo

std::thread::spawn lanza un hilo; move le pasa la propiedad de los datos que usa, y join() espera a que termine.

Para compartir datos entre hilos se usan tipos como Arc (contador de referencias seguro entre hilos) y Mutex (acceso de uno en uno). Si intentas compartir algo de forma insegura, el programa no compila.

Ejemplo práctico
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0));
    let mut hilos = vec![];

    for _ in 0..4 {
        let c = Arc::clone(&contador);
        hilos.push(thread::spawn(move || {
            *c.lock().unwrap() += 1;
        }));
    }
    for h in hilos {
        h.join().unwrap();
    }
    println!("Total: {}", *contador.lock().unwrap());   // Total: 4
}

Ejercicio

¿Qué hace Mutex?

Ver la solución

Respuesta: Permite que solo un hilo a la vez acceda al dato. Un Mutex (exclusión mutua) bloquea el dato mientras un hilo lo usa.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado Rust en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.