Este manual está diseñado para desarrolladores que quieren aprender Rust desde cero, con un enfoque práctico para entornos laborales y sistemas Linux.
Rust es un lenguaje de programación de sistemas que combina:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
rustc --version
cargo --version
# Instalar herramientas adicionales
rustup component add rustfmt clippy rust-analysis rust-src
# Configurar variables de entorno (añadir a ~/.bashrc o ~/.zshrc)
export RUST_BACKTRACE=1
export CARGO_HOME="$HOME/.cargo"
export PATH="$CARGO_HOME/bin:$PATH"
# Instalar extensiones para VS Code
code --install-extension rust-lang.rust-analyzer
code --install-extension bungcip.better-toml
code --install-extension serayuzgur.crates
fn main() {
println!("Hola, mundo!");
}
let x = 5; // Inmutable
let mut y = 10; // Mutable
const PI: f64 = 3.14159; // Constante
Categoría | Tipos |
---|---|
Escalares | i8, i16, i32, i64, i128, isize u8, u16, u32, u64, u128, usize f32, f64 bool, char |
Compuestos | Tuplas: (i32, f64, char) Arrays: [i32; 5] |
let number = 6;
if number % 4 == 0 {
println!("divisible por 4");
} else if number % 3 == 0 {
println!("divisible por 3");
} else {
println!("no divisible por 4 o 3");
}
let value = 42;
match value {
1 => println!("uno"),
2 | 3 | 5 | 7 | 11 => println!("número primo"),
13..=19 => println!("adolescente"),
_ => println!("sin coincidencia"),
}
// Loop infinito
loop {
println!("siempre");
break; // Rompe el bucle
}
// While
let mut x = 0;
while x < 10 {
x += 1;
}
// For
for i in 1..10 { // Rango 1-9
println!("{}", i);
}
// Iterar sobre array
let arr = [10, 20, 30];
for element in arr.iter() {
println!("el valor es: {}", element);
}
Concepto único de Rust que garantiza seguridad de memoria en tiempo de compilación.
let s1 = String::from("hola"); // s1 es el dueño
let s2 = s1; // s2 ahora es el dueño, s1 ya no es válido
// println!("{}", s1); // Error! s1 ya no es válido
Permite referencias a un valor sin tomar ownership.
let s1 = String::from("hola");
let len = calculate_length(&s1); // Prestamos s1 como referencia
fn calculate_length(s: &String) -> usize {
s.len()
}
let mut s = String::from("hola");
change_string(&mut s);
fn change_string(s: &mut String) {
s.push_str(", mundo");
}
Reglas de Borrowing:
struct Usuario {
nombre: String,
email: String,
edad: u32,
activo: bool,
}
impl Usuario {
// Constructor
fn new(nombre: String, email: String, edad: u32) -> Self {
Self {
nombre,
email,
edad,
activo: true,
}
}
// Método
fn desactivar(&mut self) {
self.activo = false;
}
}
// Uso
let mut usuario = Usuario::new(
String::from("Ana"),
String::from("ana@ejemplo.com"),
30
);
usuario.desactivar();
enum Mensaje {
Salir,
Mover { x: i32, y: i32 },
Escribir(String),
CambiarColor(i32, i32, i32),
}
impl Mensaje {
fn llamar(&self) {
// Implementación del método
}
}
let msg = Mensaje::Escribir(String::from("hola"));
Rust no tiene excepciones. En su lugar usa los tipos Result
y Option
.
use std::fs::File;
fn abrir_archivo() -> Result {
let f = File::open("archivo.txt")?; // Operador ? propaga errores
Ok(f)
}
match abrir_archivo() {
Ok(file) => println!("Archivo abierto"),
Err(e) => println!("Error al abrir: {}", e),
}
fn dividir(a: f64, b: f64) -> Option {
if b == 0.0 {
None
} else {
Some(a / b)
}
}
match dividir(10.0, 2.0) {
Some(resultado) => println!("Resultado: {}", resultado),
None => println!("No se puede dividir por cero"),
}
let mut numeros = vec![1, 2, 3];
numeros.push(4);
numeros.push(5);
for num in &numeros {
println!("{}", num);
}
// Mapeo y filtrado (estilo funcional)
let cuadrados: Vec<_> = numeros.iter().map(|x| x * x).collect();
let mayores_a_2: Vec<_> = numeros.iter().filter(|&&x| x > 2).collect();
use std::collections::HashMap;
let mut contactos = HashMap::new();
contactos.insert("Ana", "ana@ejemplo.com");
contactos.insert("Juan", "juan@ejemplo.com");
// Acceso seguro con match
match contactos.get("Ana") {
Some(&email) => println!("El email de Ana es {}", email),
None => println!("No se encontró a Ana"),
}
// Iteración
for (nombre, email) in &contactos {
println!("{}: {}", nombre, email);
}
use std::thread;
use std::time::Duration;
let handle = thread::spawn(|| {
for i in 1..10 {
println!("Hilo: {}", i);
thread::sleep(Duration::from_millis(100));
}
});
for i in 1..5 {
println!("Principal: {}", i);
thread::sleep(Duration::from_millis(200));
}
handle.join().unwrap(); // Esperar a que termine el hilo
use std::sync::mpsc;
use std::thread;
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hola");
tx.send(val).unwrap();
});
let recibido = rx.recv().unwrap();
println!("Recibido: {}", recibido);
use std::sync::{Arc, Mutex};
use std::thread;
let contador = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let contador = Arc::clone(&contador);
let handle = thread::spawn(move || {
let mut num = contador.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Resultado: {}", *contador.lock().unwrap());
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, BufReader, BufWriter};
// Escribir en un archivo
let mut file = File::create("datos.txt").expect("No se pudo crear el archivo");
file.write_all(b"Hola, archivo!").expect("Error al escribir");
// Leer de un archivo
let mut file = File::open("datos.txt").expect("No se pudo abrir el archivo");
let mut contenido = String::new();
file.read_to_string(&mut contenido).expect("Error al leer");
println!("Contenido: {}", contenido);
// Añadir a un archivo existente
let mut file = OpenOptions::new()
.append(true)
.open("datos.txt")
.expect("No se pudo abrir");
writeln!(file, "Más datos").expect("Error al escribir");
use std::fs;
use std::path::Path;
// Crear directorio
fs::create_dir("nuevo_dir").expect("No se pudo crear directorio");
// Verificar si existe
if Path::new("nuevo_dir").exists() {
println!("El directorio existe");
}
// Listar contenido
for entrada in fs::read_dir(".").expect("Error al leer directorio") {
let entrada = entrada.expect("Error al obtener entrada");
println!("{}", entrada.path().display());
}
mi_proyecto/
├── Cargo.toml # Configuración del proyecto
├── src/
│ ├── main.rs # Punto de entrada para binarios
│ ├── lib.rs # Punto de entrada para bibliotecas
│ └── bin/ # Binarios adicionales
├── tests/ # Tests de integración
├── examples/ # Ejemplos de uso
└── target/ # Directorio de construcción (generado)
[package]
name = "mi_proyecto"
version = "0.1.0"
edition = "2021"
authors = ["Tu Nombre "]
description = "Una breve descripción"
license = "MIT"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
reqwest = "0.11"
[dev-dependencies]
tempfile = "3.2" # Dependencias solo para tests
[features]
default = []
http_client = ["reqwest"]
Comando | Descripción |
---|---|
cargo new |
Crear un nuevo proyecto |
cargo build |
Compilar el proyecto |
cargo run |
Compilar y ejecutar |
cargo check |
Verificar código sin compilar |
cargo test |
Ejecutar tests |
cargo doc --open |
Generar y abrir documentación |
cargo fmt |
Formatear código según convenciones |
cargo clippy |
Ejecutar linter para mejores prácticas |
use std::process::Command;
fn main() {
// Ejecutar comando de sistema
let output = Command::new("uname")
.arg("-a")
.output()
.expect("Fallo al ejecutar comando");
if output.status.success() {
let info = String::from_utf8_lossy(&output.stdout);
println!("Info del sistema: {}", info);
} else {
let error = String::from_utf8_lossy(&output.stderr);
eprintln!("Error: {}", error);
}
}
use std::{thread, time};
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
fn daemonize() -> std::io::Result<()> {
// Crear nuevo proceso
let child = Command::new("/proc/self/exe")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
println!("Demonio ejecutándose con PID: {}", child.id());
Ok(())
}
fn main() -> std::io::Result<()> {
if std::env::args().any(|arg| arg == "--daemon") {
// Código del demonio
loop {
// Registrar actividad (en producción usaría syslog)
let mut file = File::create("/tmp/daemon.log")?;
writeln!(file, "Demonio activo")?;
thread::sleep(time::Duration::from_secs(5));
}
} else {
// Lanzar como demonio
daemonize()?;
}
Ok(())
}
use signal_hook::{iterator::Signals, SIGINT, SIGTERM};
use std::{thread, time::Duration};
fn main() -> Result<(), Box> {
let signals = Signals::new(&[SIGINT, SIGTERM])?;
thread::spawn(move || {
for sig in signals.forever() {
println!("Recibida señal: {:?}", sig);
// Manejar cierre limpio
std::process::exit(0);
}
});
// Trabajo principal
loop {
println!("Trabajando...");
thread::sleep(Duration::from_secs(1));
}
}
// En Cargo.toml: libc = "0.2"
extern crate libc;
use libc::{c_int, c_void, size_t};
extern "C" {
fn malloc(size: size_t) -> *mut c_void;
fn free(ptr: *mut c_void);
fn abs(num: c_int) -> c_int;
}
fn main() {
unsafe {
let ptr = malloc(100);
if ptr.is_null() {
panic!("malloc falló");
}
free(ptr);
println!("Valor absoluto de -10: {}", abs(-10));
}
}
// En lib.rs
#[no_mangle]
pub extern "C" fn suma(a: i32, b: i32) -> i32 {
a + b
}
// En Cargo.toml
[lib]
name = "misuma"
crate-type = ["cdylib"] # Biblioteca dinámica para C
Compilar con cargo build --release
y usar el .so generado desde C.
// En Cargo.toml: actix-web = "4.0"
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hola() -> impl Responder {
HttpResponse::Ok().body("Hola mundo!")
}
#[post("/saludar")]
async fn saludar(info: web::Json<Saludo>) -> impl Responder {
HttpResponse::Ok().json(info.into_inner())
}
#[derive(serde::Deserialize, serde::Serialize)]
struct Saludo {
nombre: String,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hola)
.service(saludar)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
// En Cargo.toml:
// sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-native-tls"] }
// tokio = { version = "1.0", features = ["full"] }
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::env;
#[derive(Debug, sqlx::FromRow)]
struct Usuario {
id: i32,
nombre: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
let database_url = env::var("DATABASE_URL")
.unwrap_or("postgres://usuario:contraseña@localhost/basedatos".to_string());
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await?;
// Consulta simple
let usuarios = sqlx::query_as::<_, Usuario>("SELECT id, nombre, email FROM usuarios")
.fetch_all(&pool)
.await?;
println!("Usuarios: {:?}", usuarios);
// Inserción con parámetros
let nuevo_usuario = sqlx::query!(
"INSERT INTO usuarios (nombre, email) VALUES ($1, $2) RETURNING id",
"Ana",
"ana@ejemplo.com"
)
.fetch_one(&pool)
.await?;
println!("Nuevo usuario ID: {}", nuevo_usuario.id);
Ok(())
}
// En Cargo.toml: rayon = "1.5"
use rayon::prelude::*;
fn main() {
let datos = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Procesamiento paralelo
let resultados: Vec<_> = datos.par_iter()
.map(|x| x * x)
.filter(|x| x % 2 == 0)
.collect();
println!("Resultados: {:?}", resultados);
// Reducción paralela
let suma_cuadrados: i32 = datos.par_iter()
.map(|x| x * x)
.sum();
println!("Suma de cuadrados: {}", suma_cuadrados);
}
clippy
y rustfmt
en tu pipeline de CI///
para docs.rsDebug
, Clone
cuando sea apropiadothiserror
o anyhow
para manejo de errores profesionalArc<Mutex<T>>
para datos compartidos entre hilos&str
sobre String
para parámetros de funciones cuando sea posiblecargo build --release
para builds optimizadosperf
(Linux) o flamegraph
Box
para tipos grandes que se mueven frecuentemente#[inline]
para funciones pequeñas y críticascollect
en lugar de bucles manuales cuando sea posibleConsejo profesional: Configura un pipeline de CI (GitHub Actions, GitLab CI) para ejecutar tests, clippy y rustfmt en cada commit. Esto mantendrá tu código Rust de alta calidad.
Rust es un lenguaje poderoso para desarrollo de sistemas con un enfoque en seguridad y rendimiento. Su curva de aprendizaje puede ser empinada debido a conceptos como ownership y borrowing, pero una vez dominados, permiten escribir código seguro y eficiente sin sacrificar productividad.
En entornos laborales, Rust es ideal para:
En Linux, Rust ofrece ventajas significativas sobre otros lenguajes para desarrollo de sistemas, herramientas de línea de comandos y servicios de red.