Cristhian Villegas
Backend14 min read3 views

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.

Engranajes metálicos — representación de sistemas y rendimiento

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

bash
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:

  1. Recomendado: Descarga rustup-init.exe desde rustup.rs y sigue el instalador
  2. Con winget:
bash
1# Windows — con winget
2winget install Rustlang.Rustup
3
4# Verificar
5rustc --version
6cargo --version
📌 Nota: En Windows necesitas tener instalados los Build Tools de Visual Studio (componente "Desktop development with C++") para que el linker funcione. El instalador de rustup te lo indicará si falta.

Actualizar Rust

Rust lanza una versión estable cada 6 semanas. Para actualizar:

bash
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

bash
1# Crear el archivo
2mkdir hola-rust && cd hola-rust
rust
1// main.rs
2fn main() {
3    println!("¡Hola, mundo!");
4}
bash
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 C
  • println! — es una macro (por el !), no una función. Las macros se expanden en tiempo de compilación
  • Las líneas terminan en ;
  • rustc genera 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:

bash
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:

bash
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!
💡 Tip: Usa cargo run durante desarrollo (compila + ejecuta). Usa cargo build --release para generar el binario optimizado para producción.

Cargo.toml — El manifiesto

toml
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.

rust
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

TipoDescripciónEjemplo
i8, i16, i32, i64, i128Enteros con signolet x: i32 = -42;
u8, u16, u32, u64, u128Enteros sin signolet age: u8 = 25;
f32, f64Punto flotantelet pi: f64 = 3.14159;
boolBooleanolet ok = true;
charCarácter Unicode (4 bytes)let emoji = '🦀';
StringString dinámico (heap)let s = String::from("hola");
&strString slice (referencia)let s: &str = "hola";
(T, U)Tuplalet punto = (3.0, 4.0);
[T; N]Array de tamaño fijolet nums = [1, 2, 3];
Vec<T>Vector dinámicolet 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 ->:

rust
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}
⚠️ Importante: En Rust, la última expresión de una función (sin ;) 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:

rust
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}
💡 Tip: A diferencia de 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.

rust
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:

  1. Cada valor tiene un dueño (owner)
  2. Solo puede haber un dueño a la vez
  3. Cuando el dueño sale del scope, el valor se libera automáticamente (drop)
rust
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}
⚠️ Regla de oro del borrowing: En cualquier momento puedes tener una referencia mutable O múltiples referencias inmutables, pero nunca ambas al mismo tiempo. Esto previene data races en tiempo de compilación.

¿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>:

rust
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}
💡 Tip: El operador ? 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:

bash
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
ComandoEquivalente en otros lenguajes
cargo newnpm init / go mod init
cargo buildgo build / mvn compile
cargo rungo run / python main.py
cargo testpytest / npm test
cargo addnpm install / pip install
cargo fmtprettier / black
cargo clippyeslint / 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:

📌 Versión actual: La última versión estable de Rust es la 1.85 (abril 2026). Se lanza una versión nueva cada 6 semanas. Actualiza con rustup update.
Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles