DEV Community

Cover image for De C++ a Rust: cómo reescribir infraestructura crítica en producción
lu1tr0n
lu1tr0n

Posted on • Originally published at elsolitario.org

De C++ a Rust: cómo reescribir infraestructura crítica en producción

La pregunta "¿deberíamos **migrar de C++ a Rust* este servicio?"* es una de las conversaciones más recurrentes en equipos de plataforma durante 2026. La mayoría de las veces la respuesta correcta es un rotundo no: reescribir código que funciona casi siempre es una mala idea. Pero existen casos donde la respuesta es sí, y esos casos son los que vale la pena estudiar a fondo, porque nos enseñan cuándo una reescritura no es capricho sino inversión.

Hace pocos días, el equipo de NearlyFreeSpeech.NET publicó la bitácora completa de cómo reescribieron nfsncore, el servidor C++ que toca absolutamente todas las peticiones que llegan a su hosting. Es el componente que decide si una request pasa, a qué backend va, si el TLS está al día, si el sitio está en modo mantenimiento, si la IP está bloqueada. Si falla, se cae todo el servicio. Y aun así decidieron reescribirlo en Rust, poniéndolo en producción sin downtime. En este artículo vamos a diseccionar ese caso, explicar cómo tomar una decisión similar en tu propio equipo y mostrar los patrones prácticos que convierten una migración peligrosa en una operación controlada.

Qué significa realmente "migrar de C++ a Rust"

Antes de discutir si migrar de C++ a Rust tiene sentido, hay que aclarar qué se está moviendo y qué no. En la mayoría de los equipos, el código C++ no vive solo: se apoya en APIs del sistema operativo, bibliotecas nativas, módulos de Apache o Nginx, protocolos binarios internos y convenciones de memoria que llevan años afinadas. Una reescritura seria no es traducir archivo por archivo; es reconstruir la lógica de negocio con las garantías que ofrece el nuevo lenguaje.

Rust se diferencia de C++ en tres ejes fundamentales: seguridad de memoria verificada en compilación (el famoso borrow checker), un sistema de paquetes unificado (Cargo + crates.io) y un modelo de concurrencia "fearless" donde el compilador prohíbe data races antes de que corran los tests. Conservar C++ pero escribir "estilo Rust" con RAII, std::unique_ptr y const en todos lados ayuda mucho, pero sigue siendo opcional: basta un colaborador con prisa para introducir un use-after-free que pase todas las revisiones.

La capa frontend que decide qué pasa con cada request es el punto más sensible del stack.

Cómo funciona la decisión: cuándo sí y cuándo no

El equipo de NearlyFreeSpeech resume la decisión en cuatro criterios que podés adaptar a tu propio contexto. Funcionan como filtros: si no pasás al menos tres, probablemente no deberías reescribir.

1. El código es crítico y pequeño a la vez

El nfsncore original ocupaba menos del 10% de la base de código total, pero procesaba el 100% del tráfico entrante. Esa asimetría es la que justifica el esfuerzo: pagás la deuda una vez y el beneficio se multiplica en cada request. Si tu componente son 300.000 líneas que cambian todos los días, olvidalo; si son 20.000 líneas estables que corren en cada transacción, es candidato fuerte.

2. El equipo ya escribe C++ moderno

Si tu codebase usa punteros inteligentes, RAII estricto y const-correctness, el borrow checker de Rust no va a ser un muro: va a ser la formalización de reglas que ya seguías a mano. Equipos que escriben C++ al estilo de 1998 sufren mucho más en la transición.

3. Querés agregar features que el lenguaje actual pelea

Cuando cada feature nueva requiere rodeos, macros ingeniosas o plantillas ilegibles, el lenguaje te está diciendo que llegaste al techo. Rust con su tipado algebraico, enum con datos, Result<T, E> y pattern matching hace que ciertas extensiones sean triviales.

4. Podés desplegar en paralelo

Esto es lo que convierte una reescritura en una migración segura: la capacidad de correr la versión vieja y la nueva lado a lado, comparar salidas y cambiar de una a otra sin drama. Si tu arquitectura no lo permite, priorizá ese cambio antes de pensar en el lenguaje.

💭 Clave: Una reescritura sin capacidad de rollback es una apuesta, no una migración. Antes de tocar una línea de Rust, asegurate de poder correr ambas versiones en paralelo y desviar tráfico con un feature flag.

Ejemplos prácticos: la diferencia se ve en el código

Los autores del artículo original dan un ejemplo que parece menor pero resume el espíritu del cambio. Convertir un hostname a minúsculas, una operación trivial en cualquier servidor HTTP, se ve así en C++:

std::transform(
    host.begin(), host.end(), host.begin(),
    [](unsigned char c) { return std::tolower(c); }
);
Enter fullscreen mode Exit fullscreen mode

Y así en Rust:

let host = host.to_lowercase();
Enter fullscreen mode Exit fullscreen mode

No es solo estética. Es que en 2026 pasar una hora eligiendo la función correcta de <algorithm> y cuidándote de la casteada a unsigned char para evitar undefined behavior con std::tolower ya no es productivo.

Manejo de errores como tipo de dato

Un patrón que se vuelve natural al migrar de C++ a Rust es representar errores como valores, no como excepciones. Compará:

// C++ clásico: hay que recordar revisar
std::optional r = resolver.find(host);
if (!r) {
    // fácil olvidarse de este if
    return serve_404();
}
auto upstream = r->upstream();
Enter fullscreen mode Exit fullscreen mode
// Rust: el compilador obliga a manejar ambos casos
match resolver.find(&host) {
    Some(route) => route.upstream(),
    None => serve_404(),
}
Enter fullscreen mode Exit fullscreen mode

En C++ el compilador no te obliga a revisar el optional; en Rust sí. Esa diferencia, multiplicada por miles de rutas de código, es la que reduce bugs en producción.

Concurrencia sin pies de plomo

Donde la ganancia es más dramática es en código concurrente. Compartir estado mutable entre threads en C++ exige disciplina humana para poner std::mutex en el lugar correcto. En Rust, el compilador directamente no te deja compilar código que comparta datos sin sincronizar:

use std::sync::Arc;
use tokio::sync::RwLock;

struct Router {
    rules: Arc>>,
}

impl Router {
    async fn reload(&self, nuevas: Vec) {
        let mut guard = self.rules.write().await;
        *guard = nuevas;
    }
}
Enter fullscreen mode Exit fullscreen mode

Si alguien intenta acceder a rules sin pasar por el RwLock, no compila. Ese tipo de garantías estructurales valen oro en un componente que ve cada request.

Despliegue canary: ambas versiones conviven y el tráfico se mueve de a poco.

Casos de uso: cuándo otros equipos dieron el paso

NearlyFreeSpeech no está solo. En los últimos tres años se acumularon casos públicos de migrar de C++ a Rust en infraestructura crítica que vale la pena mencionar.

  • Cloudflare reescribió varios módulos de su proxy para mover lógica sensible a Rust, reduciendo incidentes de memoria y mejorando el tiempo de iteración.
  • Discord migró su servicio de estado de presencia de Go (no C++, pero con preocupaciones similares) a Rust y documentó reducciones de latencia p99 significativas.
  • Microsoft invirtió en Rust para componentes de Windows, empezando por DWriteCore, y ha publicado que el 70% de sus CVE históricos corresponden a errores de memoria que Rust elimina por diseño.
  • Linux kernel aceptó Rust como segundo lenguaje oficial en 2023 y durante 2026 ya tiene drivers de GPU y filesystems en Rust conviviendo con C.

El patrón común no es "Rust es mejor que C++". Es: para componentes donde la seguridad de memoria es crítica y el costo de un bug es altísimo, invertir en tipado fuerte y verificación estática se paga solo.

Cómo planificar la migración paso a paso

Una migración responsable tiene fases bien definidas. El siguiente diagrama muestra el flujo que recomendamos, inspirado en el caso de NearlyFreeSpeech y otros similares:

flowchart LR
    A[Auditoría del componente C++] --> B[Definir contratos y tests de paridad]
    B --> C[Implementación Rust en paralelo]
    C --> D[Shadow traffic: misma request a ambos]
    D --> E[Canary: 1% del tráfico real a Rust]
    E --> F[Rampa progresiva al 100%]
    F --> G[Apagado del binario C++]
Enter fullscreen mode Exit fullscreen mode

La fase de shadow traffic es la que más confianza da: mandás la misma petición a ambas implementaciones, devolvés la respuesta de C++ al usuario y comparás la de Rust en logs. Si hay diferencias, las investigás sin que nadie en el exterior note nada.

⚠️ Ojo: No subestimes los comportamientos no documentados. La mayoría de las bibliotecas C++ de 15 años tienen edge cases que solo existen en el binario, no en la especificación. Un test de paridad sólido los atrapa antes de romper producción.

Ventajas y desventajas de migrar

Ventajas concretas

  • Memoria segura por construcción: desaparecen clases enteras de bugs (use-after-free, buffer overflow, iterator invalidation).
  • Cargo y crates.io: agregar un parser de TLS o un cliente HTTP es una línea en Cargo.toml, no una odisea de CMake.
  • Ecosistema async maduro: tokio, hyper y tower cubren la gran mayoría de patrones de red sin reinventar nada.
  • Mejor tiempo de onboarding: devs junior se vuelven productivos más rápido cuando el compilador actúa como mentor.

Desventajas reales

  • Curva de aprendizaje del borrow checker durante las primeras semanas (aunque menos pronunciada si ya escribís C++ moderno).
  • Tiempos de compilación mayores que C++ incremental en bases grandes.
  • Ecosistema menos profundo en ciertos nichos muy específicos (DSP, gráficos de videojuegos, ciertos drivers).
  • Costo humano: hay que capacitar al equipo, hacer code reviews más estrictos al principio y aceptar que el primer mes la velocidad baja.

💡 Tip: Si estás evaluando tu primer proyecto en Rust, empezá por una herramienta CLI interna antes que por un servicio productivo. Aprendés el ecosistema sin presión y el equipo se forma una opinión real basada en código propio.

Comandos de instalación para tu primer proyecto

Si querés probar el enfoque en tu máquina, instalar Rust es cuestión de minutos en cualquier sistema operativo:

# Linux y macOS
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Windows (PowerShell)
Invoke-WebRequest -Uri https://win.rustup.rs -OutFile rustup-init.exe
.\rustup-init.exe

# Verificar instalación (en las tres)
rustc --version
cargo --version
Enter fullscreen mode Exit fullscreen mode

Crear un proyecto nuevo y agregar dependencias tampoco tiene misterio:

cargo new mi-proxy --bin
cd mi-proxy
cargo add tokio --features full
cargo add hyper --features full
cargo run
Enter fullscreen mode Exit fullscreen mode

📖 Resumen en Telegram: Ver resumen

Preguntas frecuentes

¿Es obligatorio migrar todo el codebase de una vez?

No. La estrategia recomendada es identificar un componente con contratos bien definidos y migrarlo primero. Rust y C++ conviven bien vía FFI con extern "C", lo que permite avances incrementales sin big-bang.

¿Cuánto tiempo toma una migración típica?

Depende del tamaño y la madurez del código. NearlyFreeSpeech reportó que el esfuerzo se midió en meses, no años, para un componente de ~10.000 líneas. Equipos con prácticas flojas pueden tardar el triple.

¿Rust es realmente más rápido que C++?

En benchmarks microscópicos, C++ bien optimizado suele empatar o ganar por márgenes pequeños. En código real con concurrencia, Rust frecuentemente gana porque el programador se anima a paralelizar cosas que en C++ nadie tocaría por miedo a data races.

¿Qué pasa con las dependencias C++ existentes?

Se pueden invocar desde Rust usando bindgen para generar bindings automáticos y cxx para interoperar con APIs de C++ más complejas. No hay que tirar las bibliotecas existentes.

¿Vale la pena aprender Rust en 2026 si ya sé C++?

Sí. Incluso si no planeás migrar nada, entender el modelo de propiedad de Rust te hace mejor programador en C++ porque formaliza reglas que ya deberías seguir. Además, Rust ya es requisito o plus en muchas ofertas de infraestructura, sistemas embebidos y blockchain.

¿Cuándo NO deberíamos migrar de C++ a Rust?

Cuando el código es enorme y estable, cuando el equipo no tiene experiencia con tipado fuerte, cuando no hay capacidad de desplegar versiones en paralelo, o cuando el componente no es crítico y los bugs actuales son manejables. En esos casos, mejorar las prácticas en C++ da mejor retorno.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)