Por qué necesité Rust
💻1 Años. Un problema.
1988 → hoy
| Época | Herramienta | Lección |
|---|---|---|
| Años 90 | Perl | El poder sin seguridad es un desastre |
| Años 2000 | Python | El pragmatismo sin garantías es frágil |
| Años 2010 | Bash · Chef · Ansible · Terraform | Más herramientas no resuelven problemas de paradigma |
| Años 2020 | Go · ??? |
Cada vez la realidad me demostró lo contrario.
La evolución
Cómo hemos llegado hasta aquí
Etapa 1 — Local (finales de los 80 / principios de los 90)
Terminales tontos. Una sola máquina. Un solo estado.
- Desarrollo local, ciclos de despliegue largos, poca urgencia
- Un solo estado — fácil de observar, fácil de controlar
- IaC: scripts procedimentales, lógica escondida dentro de la aplicación
La era Perl: podíamos hacer cualquier cosa.
También podíamos romper cualquier cosa.
Metaprogramación bella y aterradora.
Sin red de seguridad.
Fallos silenciosos a las 3 de la mañana.
Lección: el poder sin seguridad es un desastre.
Etapa 2 — Redes / Internet
Los sistemas se alejan. Más gente. Más coordinación.
- Acceso remoto, equipos distribuidos, la seguridad pasa a ser relevante
- El coste de la caída sube — los procesos se vuelven críticos
- Armonizar: instalación de paquetes, configuración y actualizaciones en varias máquinas en paralelo
- IaC: automatización reproducible, primeros intentos declarativos
La era Python: desarrollo rápido, gran comunidad.
Pero nada te impedía equivocarte.
Las anotaciones de tipos llegaron tarde — y opcionales.
Errores en tiempo de ejecución >> errores en tiempo de compilación.
Lección: el pragmatismo sin garantías es frágil.
Más piezas. Más gente. La cosa se pone interesante.
Etapa 3 — Contenedores / Nube / CI-CD
Todo. En todas partes. Al mismo tiempo.
- Monolito → distribuido, 24×7×365, alta disponibilidad
- Nube, híbrido, multinube, on-prem — simultáneamente
- Rollback y rollforward: transacciones de base de datos, pero para infraestructura
- Escalar en horizontal Y en vertical — y desescalar
- CI/CD continuo: nuevas funcionalidades, nuevos despliegues, cambio permanente
La era Nube/IaC: Ansible, Terraform, Chef, Puppet.
¿Qué cambió? La sintaxis.
¿Qué no cambió? Los problemas de fondo.
Seguimos peleando con la seguridad de tipos. Seguimos descubriendo errores en producción.
Lección: más herramientas no resuelven problemas de paradigma.
Podía automatizar la infraestructura.
Pero no podía hacerla fiable.
No podía evitar los errores.
No podía dormir.
Por qué falla la IaC
El problema del restaurante

El restaurante
Todo restaurante tiene al menos tres actores.
| Restaurante | Infraestructura |
|---|---|
| El cliente declara lo que quiere | Configuración declarativa (YAML, HCL) |
| El camarero valida y transmite | Orquestador (K8s, Ansible) |
| La cocina ejecuta y entrega | Tiempo de ejecución / aprovisionamiento |
| El plato llega — o no | El despliegue funciona — o no |
Lo que hace que funcione — o no:
El cliente declara. No implementa.
El camarero debe saber qué es posible —
antes de ir a la cocina.
«Quiero X» → el camarero va a la cocina
→ «no tenemos X, ¿por qué está en la carta?»
→ vuelta a la mesa.
Equivalente: configuré un host con el puerto 8443
→ ese puerto no está permitido
→ reconfigurar desde cero.
La verdad que muta
El estado no es estático.
Puede cambiar en cada paso de la cadena.
| Paso | Verdad para este actor |
|---|---|
| El cliente habla | Lo que quiere |
| La libreta del camarero | Lo que quedó anotado |
| Las marcas de la cocina | Lo que está hecho / sin hacer |
| El ticket de pago | Lo que se sirvió realmente |
Fallar pronto = fallar barato. Fallar en producción = pesadilla.
El problema del contexto:
El camarero conoce al cliente habitual:
«siempre sin sal».
La cocina no. Si el camarero cambia
— ese contexto desaparece.
La deriva de configuración es exactamente eso: Estado implícito. No explícito. No propagado. Perdido en silencio.
El coste del fallo depende de dónde ocurre:
- Fallar en la mesa (pedido imposible):
barato — se atrapa antes de la cocina - Fallar en cocina (falta un ingrediente):
medio — renegociar con el cliente - Fallar en la entrega (llega el plato equivocado):
caro — experiencia destruida
«No tenemos setas»
Cuando un actor de la cadena no puede cumplir parte del pedido.
«¿Puedo sustituirlas por verduras?»
Esa renegociación tiene que ser explícita. Trazada. Reautorizada.
No silenciosa. No dada por supuesta.
La deriva de configuración es renegociación silenciosa:
El sistema cambia. Nadie avisa. El estado diverge sin dejar rastro.
La respuesta de Rust — Option<T>:
// The waiter cannot silently skip a missing ingredient
let mushrooms: Option<Ingredient> = order.mushrooms;
match mushrooms {
Some(m) => add_to_dish(m),
None => renegotiate_with_guest(&guest)?, // explicit. always.
}
// drift = treating None as Some. Rust makes that impossible.El compilador es el camarero que no puede fingir que existe un ingrediente.
La evolución de la configuración
Cómo pasamos del código al infierno del YAML
Cableada en el código — todo dentro del binario. Control total. Cero flexibilidad.
Configuración externa (JSON) — funciona entre máquinas. Ilegible para humanos a escala.
YAML / TOML — más legible. Sintaxis frágil. Tipos implícitos. Errores silenciosos.
YAML + Serde — Serde valida la estructura:
- ¿Existe el campo? ¿Es del tipo correcto?
- ¿Aceptamos
"elephant"como mascota? Si el tipo esString… sí. - Serde valida la forma. No el significado.
Plantillas Helm / Jinja — YAML generado a partir de variables (en YAML).
- ¿Valida el contenido del YAML generado? No. En absoluto.
- Como usar un LLM con una referencia en markdown: el formato está ahí, pero ¿es correcto el contenido?
Eso no lo garantiza nadie.
CI/CD continuo.
Sin validación semántica.
Esperanza continua.
(cruzando los dedos en producción)
Tres preguntas sin respuesta
- «En mi máquina funciona» — en producción, no lo sé
- Fallar tarde = coste máximo. Queremos: fallar rápido, fallar barato
- ¿Es la declaración suficiente y coherente con lo que es posible?
- ¿Cuáles son los límites? ¿Estáticos o dinámicos? ¿Cuál es la fuente de verdad — y cuándo muta?
- CI/CD sin validación semántica = esperanza continua
- Queremos certeza, no azar
- «En mi máquina funciona» no puede ser el estándar de producción
No estamos inventando nada nuevo. Todo existe ya. La pregunta es si lo estamos gestionando correctamente.
Las herramientas no eran el problema.
Los lenguajes no eran el problema.
El paradigma era el problema.
Sistemas que no sabemos controlar.
Esperamos que funcionen.
Cuando no lo hacen — los arreglamos.
Pesadilla continua.
(el estado de alarma como nueva normalidad)
La respuesta a las tres preguntas
El puente: de Serde a los tipos
Serde carga configuración estructuralmente válida.
Pero "elephant" como pet: String compila.
La respuesta de Rust: no uses String. Usa un tipo.
// Before: String — anything goes
pet: String // "elephant" compiles. "unicorn" compiles. 🤷
// After: closed domain — impossible values don't exist
enum Pet { Dog, Cat, Rabbit } // "elephant" doesn't compileEste es el cambio.:
No el formato de configuración. El modelo de lo que puede contener.
Serde valida la forma | Los tipos validan el significado
El compilador valida antes de que el binario exista.
Lo que Rust nos da
// Immutability by default — invariants are invariants
let config = load_config()?; // cannot change silently
// Option<T> — no nulls, no assumptions
let mushrooms: Option<Ingredient> = order.mushrooms;
match mushrooms {
Some(m) => add_to_dish(m),
None => notify_kitchen_to_skip(), // explicit. always.
}
// Enums as closed domains
enum CloudProvider { Hetzner, UpCloud, AWS, GCP, Azure, OnPrem }
enum Port { Valid(u16) } // not any integer — a valid port// Traits define what every actor in the chain must fulfill
#[async_trait]
pub trait TaskStorage: Send + Sync {
async fn create_task(&self, task: WorkflowTask) -> StorageResult<WorkflowTask>;
async fn update_task(&self, id: &str, status: TaskStatus) -> StorageResult<()>;
// Add a new provider: implement this trait or it doesn't compile
}El compilador como prevalidador
// Closed domain — you can't forget a case
enum RollbackStrategy {
ConfigDriven,
Conservative, // preserve unless marked deletion
Aggressive, // revert all changes
Custom { operations: Vec<String> },
}
// The compiler enforces exhaustive handling
match strategy {
RollbackStrategy::ConfigDriven => ...,
RollbackStrategy::Conservative => ...,
RollbackStrategy::Aggressive => ...,
RollbackStrategy::Custom { .. } => ...,
// miss one → compile error
}El compilador valida:
- Antes de construir el binario
- No después de horas de ejecución
- No cuando por fin se llama a una función que nadie tocaba desde hace meses
- Comportamiento predecible:
memoria, recursos, flujos de trabajo
Antes de que llegue a la cocina.
Antes de que el cliente espere.
Antes de que falte ningún ingrediente.
El impacto humano
Cuando el sistema es de fiar:
✓ Vuelve el sueño
✓ Vuelve la confianza
✓ El equipo confía en la automatización
✓ Baja el estrés
✓ Puedes descansar de verdad
Lo que no se puede medir: el miedo.
Lo que sí se puede medir: el MTTR.
Antes: > 30 minutos. Ahora: < 5 minutos.
CI/CD continuo.
Tipos. Compilador. Estado explícito.
Certeza continua.
(para seguir durmiendo bien)
Esto no es teoría
Nickel
YAML descartado. TOML descartado.
Motivo: sin seguridad de tipos.
YAML escribía lo que queríamos.
No sabía decir qué era posible.
Nickel cierra esa brecha
— en tiempo de configuración, no a las 3 de la mañana.
# Infrastructure schema
# — validated at config compile time
{
compute | {
region | String,
count | Number & (fun n => n > 0),
scaling | {
min | Number & (fun n => n > 0),
max | Number & (fun n => n >= min),
# -- compiler verifies this relationship
}
}
}Fuente de verdad tipada
Resultado (ADR-003 de provisioning):
cero errores de tipo de configuración en producción.
Jerarquía de configuración:
defaults → workspace → profile → environment → runtime
Cada capa se fusiona.
El sistema de tipos atrapa los conflictos.
En tiempo de configuración — no en tiempo de despliegue.
El cliente escribió un pedido imposible.
Nickel hace que los pedidos imposibles no se puedan escribir.
Serde valida la forma.
Nickel valida el significado.
El compilador valida antes del despliegue.
Traits como proveedor
La cocina puede cambiar.
AWS ≠ UpCloud ≠ bare metal. La misma carta.
// Every provider implements the same contract
enum DependencyType { Hard, Soft, Optional }
enum TaskStatus {
Pending, Running, Completed, Failed, Cancelled
}
// Dependency resolution
// — the orchestrator knows the order
// Installing Kubernetes:
// containerd (Hard) → etcd (Hard) → kubernetes
// → cilium (requires kubernetes)
// → rook-ceph (requires cilium)Estado explícito — sin deriva:
pub struct WorkflowExecutionState {
pub task_states: HashMap<String, TaskExecutionState>,
// what happened and when
pub checkpoints: Vec<WorkflowCheckpoint>,
pub provider_states: HashMap<String, ProviderState>,
}Contratos
- Checkpoint cada 5 minutos
- Sin estado implícito.
Nada de «el camarero se acuerda de que el cliente no quiere sal».
- Está en el pedido. Siempre. Explícito.
Grafo de dependencias
fail_fast: bool no es una opción de configuración.
Es un principio codificado como tipo.
DAG tipado — resolución de dependencias impuesta
en tiempo de compilación del flujo de trabajo:
La cocina no sirve el plato principal
antes de que el entrante esté listo.
DependencyType::Hard es esa regla.
En el sistema de tipos, no en un runbook.
pub struct WorkflowConfig {
pub max_parallel_tasks: usize,
pub task_timeout_seconds: u64,
// halt on first failure
pub fail_fast: bool,
// recovery point granularity
pub checkpoint_interval_seconds: u64,
}
containerd (Hard) → etcd (Hard) → kubernetes
→ cilium (requires: kubernetes)
→ rook-ceph (requires: kubernetes + cilium)Fallar rápido, fallar barato
DependencyType::Hard
- el fallo detiene la cadena. Siempre.DependencyType::Soft
- continúa, degradado de forma explícita.DependencyType::Optional
- que falte es lo esperado y está bien.
El compilador atrapa el orden de instalación.
No el ingeniero de guardia a las 2 de la mañana.
Aplicaciones reales
Kubernetes
El orquestador aprovisiona los componentes
del clúster como un flujo de trabajo tipado:
containerd
→ etcd
→ kubernetes control plane
→ CoreDNS
→ Cilium (CNI)
→ Rook-Ceph (storage)El compilador atrapa:
instalar Cilium sin Kubernetes.
No el ingeniero de guardia a las 2 de la mañana.
«En mi máquina funciona» aquí se paga.
Esta es la infraestructura de mayor riesgo de toda la charla.
Validadores de blockchain
Los validadores exigen una disponibilidad brutal.
Un validador que falla pierde fondos — y no es el dinero de tu infraestructura.
Es el de tu cliente.
- Criptografía poscuántica: híbrido CRYSTALS-Kyber + Falcon + AES-256-GCM. Claves de validador protegidas frente a ordenadores cuánticos.
- SLOs con presupuestos de error reales: 99.99% = 52.6 min de caída al año. Prometheus bloquea los despliegues cuando la tasa de consumo excede el presupuesto.
- Configuración determinista: los parámetros del validador son tipos. Un
bond_amountque no sea unu128válido no compila.
Recuperación ante desastres
El rollback como tipo, no como procedimiento
Las 3 de la mañana. Algo se ha roto. Necesitas hacer un rollback.
Sin tipos: improvisas.
Con tipos: eliges una estrategia
— o no compila.
// Checkpoint = complete system snapshot
pub struct Checkpoint {
pub workflow_state: Option<WorkflowExecutionState>,
pub resources: Vec<ResourceSnapshot>,
pub provider_states: HashMap<String, ProviderState>,
}
// Rollback strategy = typed choice, not a runbook
enum RollbackStrategy {
ConfigDriven,
Conservative, // preserve unless marked for deletion
Aggressive, // revert all changes
Custom { operations: Vec<String> },
}
// You cannot do rollback without choosing a strategy.
// The compiler doesn't let you ignore the case.Copia de seguridad multi-backend: restic, borg, tar, rsync
— todos como variantes de un enum.
La copia de seguridad de producción y la restauración de recuperación usan el mismo tipo, el mismo esquema.
El runbook existe.
Nadie lo lee con claridad a las 3 de la mañana y bajo presión.
El tipo fuerza la decisión antes de la crisis.
El estado es el mismo en producción y en recuperación. Siempre.
Autorreparación
Cuando algo se rompe a las 3 de la mañana
— responde el sistema, no tú.
enum RemediationAction {
ScaleService { service: String, replicas: u32 },
FailoverService { service: String, region: Region },
RestartService { service: String },
ClearCache { service: String, scope: CacheScope },
}
// Typed playbooks. Not shell scripts. Not hope.
// Fails 3 times → escalates to human.
// Never loops indefinitely.— Remediación tipada
Lo que pasa a las 3 de la mañana:
Salta la alerta
→RemediationEnginecasa la condición
→ ejecutaRestartServiceFunciona: silencio. Nadie se despierta.
Falla 3×: se envía el aviso
— con el estado completo, el checkpoint
y el historial de ejecución.
Te despiertas con información. No con caos.
Sin tipos. Sin compilador. Sin estado explícito.
MTTR > 30 minutos.
Rust. Tipos. Estado explícito. Respuesta automatizada.
MTTR < 5 minutos.
(a las 3 de la mañana. sin ti.)
Por qué esto importa
Para todos los que estáis en esta sala

Para ti
Si te has sentido tan frustrado como yo
- Rust resuelve problemas que ya tienes.
- Esto no es hype. Es alivio operativo.
Empieza por aquí:
- Modela tu infraestructura como tipos
- Deja que el compilador prevalide antes del despliegue
Si estás más al principio de tu carrera
- Empieza con seguridad de tipos desde el primer día.
- Construye buscando fiabilidad, no solo velocidad.
El camino más corto:
- Tipos para la configuración.
- Traits para los proveedores.
- Determinismo para las operaciones.
Tengo la perspectiva de una larga experiencia en producción.
He visto tecnologías ir y venir.
Rust no es hype. Rust es alivio con evidencia.
Resuelve problemas operativos reales que arrastré durante décadas.
Más años no son un lastre.
Son una ventaja.
Por qué necesité Rust
Rust me dio sistemas deterministas y mejor sueño.
Empieza poco a poco: modela la infraestructura como tipos.
Más información: · jesusperez.pro
· provisioning.systems · vapora.dev · rustelo.dev