<!-- Canonical: https://lucioduran.com/es/blog/memory-safe-c-alternatives-carbon-cpp2-val
Language: es
Content-SHA256: 70b7ef36bf43d38b87efc0b20d71c5cca2df341d37ad4d5d92765d708ef11414
-->

Programming Languages

# Alternativas Memory-Safe a C++: Carbon, Cpp2, Val y Circle en Comparación

2025-10-189 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/memory-safe-c-alternatives-carbon-cpp2-val>)

## El Problema Persistente

C++ no tiene una historia de memory safety. Tiene una historia de "disciplina y tooling". AddressSanitizer detecta errores en tiempo de ejecución. Los analizadores estáticos detectan algunos en tiempo de compilación. Las C++ Core Guidelines son una guía de estilo, no un mecanismo de cumplimiento. Y después de tres décadas de parches incrementales, se siguen publicando CVEs por use-after-free y buffer overflows.

La guía de la NSA del 2022, el empuje posterior de CISA, y ahora los requerimientos del EU Cyber Resilience Act de 2025 hicieron que "simplemente tener más cuidado" sea una posición insostenible para cualquier producto que toque industrias reguladas. De ahí la necesidad de evaluar alternativas reales.

## Carbon: La Apuesta Interop-First de Google

El pitch de Carbon es directo: ser para C++ lo que Kotlin fue para Java. Interop bidireccional completa, migración gradual, y una sintaxis modernizada que elimina los footguns más peligrosos.

### Interop en la Práctica

El siguiente ejemplo importa una clase C++ de audio buffer en [Carbon](<https://github.com/carbon-language/carbon-lang>):

```carbon
import Cpp library "audio/ring_buffer.h";

fn ProcessBlock(buf: Cpp.RingBuffer*, block_size: i32) -> bool {
 var samples: auto = buf->ReadBlock(block_size);
 if (samples.size() != block_size) {
 return false;
 }

 var i: i32 = 0;
 while (i < block_size) {
 samples[i] = ApplyGain(samples[i], 0.7);
 ++i;
 }

 return buf->WriteBlock(samples);
}
```

La directiva ` import Cpp ` es genuinamente impresionante cuando funciona. Las pruebas muestran que una implementación completa de ring buffer, incluyendo operaciones de fence con ` std::atomic `, puede importarse y llamarse desde Carbon sin escribir una sola línea de binding. El compilador entiende el name mangling de C++, layouts de vtable, y hasta la semántica de ` std::unique_ptr `.

Donde se rompe todo: el código SIMD. Las codebases que usan extensiones vectoriales de GCC y ` __attribute__((target("avx2"))) ` excesivamente encontrarán que la capa de interop de Carbon se ahoga con esto. Cada función SIMD necesita un wrapper con C-linkage plano, lo cual derrota el propósito del interop seamless.

### Evaluación del Costo de Migración

Para una codebase representativa de 180k líneas, la estimación:

- **Código limpio C++17 moderno** (~60% de la codebase): ~2.5 meses-ingeniero para portar
- **Código genérico template-heavy** (~25%): ~6 meses-ingeniero, con rediseño significativo
- **SIMD/intrinsics/platform-specific** (~15%): No factible de portar. Mantener como C++ e interoperar.

Estimación total realista: 8-10 meses-ingeniero para una migración incompleta. Ese es el número honesto que raramente se menciona en las conferencias.

## Cpp2/cppfront: El Middle Ground Pragmático de Herb Sutter

[Cpp2](<https://github.com/hsutter/cppfront>) toma un enfoque fundamentalmente distinto. En vez de construir un lenguaje nuevo con interop, define una sintaxis nueva que transpila a C++20 estándar. El build system existente, el debugger, el profiler, todo sigue funcionando porque la salida es C++.

### La Sintaxis en Acción

La misma lógica de procesamiento del ring buffer en Cpp2:

```cpp2
ProcessBlock: (inout buf: RingBuffer, block_size: int) -> bool = {
 samples := buf.ReadBlock(block_size);
 if samples.size() != block_size {
 return false;
 }

 for (0 ..< block_size) do (i) {
 samples[i] = ApplyGain(samples[i], 0.7);
 }

 return buf.WriteBlock(samples);
}
```

El parámetro ` inout ` hace la mutación explícita en el sitio de llamada. Los parámetros son ` in ` (const ref) por defecto, lo que elimina toda una clase de errores de mutación accidental.

### Lo Que Realmente Transpila

El compilador cppfront convierte eso en aproximadamente este C++20:

```cpp
auto ProcessBlock(RingBuffer& buf, int block_size) -> bool {
 auto samples = buf.ReadBlock(block_size);
 if (samples.size() != block_size) {
 return false;
 }

 for (auto i = 0; i < block_size; ++i) {
 samples[i] = ApplyGain(samples[i], 0.7);
 }

 return buf.WriteBlock(samples);
}
```

El insight clave: cppfront agrega safety de inicialización (sin variables sin inicializar), null safety (sin null dereference en ` cpp2::unique_ptr `), bounds safety (subscript chequeado por default), y type safety (sin conversiones narrowing implícitas). Todo enforced a nivel de sintaxis Cpp2, todo compilado a C++20 con zero overhead.

### La Trampa

Los mensajes de error de cppfront son deficientes. Cuando algo sale mal durante la transpilación, se obtiene un error apuntando a una línea en el fuente Cpp2, pero si el error está en el C++ generado, el compilador falla con código generado que el desarrollador no escribió. Una frustración habitual: invertir una tarde entera depurando un fallo de instanciación de plantilla que resulta ser una cláusula ` requires ` faltante que cppfront debería haber generado pero no lo hizo.

Además, el soporte de IDEs es limitado. Existe una extensión de VS Code con resaltado de sintaxis básico, pero no hay go-to-definition confiable, no hay herramientas de refactoring, y el language server falla con archivos de más de ~500 líneas. Este aspecto tiene mayor impacto del que suele reconocerse.

## Val: El Enfoque de Mutable Value Semantics

[Val](<https://www.val-lang.dev/>) (antes conocido como Hylo) toma la salida más radical de C++. Su premisa central: si cada valor tiene un único dueño y las mutaciones siempre ocurren in-place, no se necesita borrow checker, no se necesita garbage collection, y se obtiene memory safety con cero overhead en tiempo de ejecución.

### Ownership Sin Borrowing

```val
fun processBlock(buf: inout RingBuffer, blockSize: Int) -> Bool {
 var samples = buf.readBlock(blockSize)
 if samples.count != blockSize {
 return false
 }

 for i in 0 ..< blockSize {
 &samples[i] = applyGain(samples[i], 0.7)
 }

 return buf.writeBlock(samples)
}
```

El prefijo ` & ` en ` samples[i] ` es la forma de Val de indicar "se está mutando esto in-place". Si se omite, el compilador rechaza la compilación. Es explícito, es visible, y elimina la clase de errores de "se copió o se creó un alias sin intención".

Lo que hace a Val genuinamente interesante comparado con Rust es la ausencia de anotaciones de lifetime. No hay ` 'a ` lifetimes distribuidos por las firmas de funciones. El sistema de tipos de Val asegura safety a través de value semantics solas, si se posee un valor, se es su único propietario. Sin estado mutable compartido, sin data races por construcción.

### Limitaciones Actuales de Val

Val es el más joven de todos, y se nota. El compilador (construido sobre SIL de Swift) es lento, compilar un módulo de prueba de 3000 líneas tardó 47 segundos versus 3 segundos en Cpp2 y 8 en Carbon. La biblioteca estándar es mínima. No existe una historia de FFI que valga la pena mencionar, es posible llamar funciones C a través de una anotación ` @foreign `, pero la interoperabilidad con C++ no existe todavía.

Para un proyecto greenfield con control total del stack, el diseño de Val es probablemente el más elegante. Para migrar una codebase C++ existente, es inviable en 2026.

## Circle: La Revolución Unipersonal de Sean Baxter

[Circle](<https://www.circle-lang.org/>) es el comodín. Sean Baxter construyó en solitario un compilador de C++ que extiende el lenguaje con características de safety de primera clase, pattern matching, reflection, y lo que fundamentalmente es un lenguaje nuevo que resulta ser un superset estricto de C++.

### Safety Annotations en la Práctica

```cpp
#feature on safety

void ProcessBlock(RingBuffer& buf, int block_size) safe {
 auto samples = buf.ReadBlock(block_size);
 if (samples.size() != block_size) {
 return;
 }

 for (int i = 0; i < block_size; ++i) {
 samples[i] = ApplyGain(samples[i], 0.7);
 }

 buf.WriteBlock(std2::move(samples));
}
```

La palabra clave ` safe ` en la firma de la función habilita el borrow checker de Circle para esa función. Es posible mezclar código safe y unsafe a nivel de función, lo que constituye la vía de escape más granular de cualquier lenguaje en esta comparación.

Circle compila codebases C++ existentes, incluso proyectos de 180k líneas, al primer intento. Es un compilador de C++, así que naturalmente lo hace. El camino de migración es: compilar el código con Circle, después incrementalmente agregar ` #feature on safety ` a los archivos uno por uno, arreglando las violaciones de safety que el compilador te reporta.

### El Problema del Bus Factor

Circle lo mantiene una sola persona. Una persona increíblemente talentosa, pero una persona. No hay especificación formal, no hay comité, no hay respaldo corporativo. Si Sean decide ir a hacer otra cosa, el proyecto muere efectivamente. Para un proyecto hobby, está bien. ¿Para infraestructura de producción que necesita mantenerse por una década? Ese riesgo es real y es lo que limita una recomendación más amplia de Circle.

## Benchmark: Implementación de Lock-Free Queue

La misma cola lock-free de Michael-Scott fue implementada en los cuatro lenguajes más el baseline C++20. Los resultados en una instancia [AWS](<https://aws.com/>) c7g.2xlarge (Graviton3, 8 vCPUs):

| Lenguaje | Enqueue/Dequeue ops/sec | Tamaño Binario | Tiempo Compilación |
| --- | --- | --- | --- |
| C++20 (baseline) | 14.2M | 48 KB | 1.8s |
| Carbon | 13.8M | 52 KB | 4.2s |
| Cpp2 (transpilado) | 14.1M | 48 KB | 2.1s |
| Val | 11.6M | 67 KB | 12.4s |
| Circle | 14.2M | 49 KB | 2.3s |

Cpp2 y Circle son esencialmente idénticos al baseline C++ en performance, lo cual tiene sentido, emiten C++. Carbon está dentro del 3%, lo que es impresionante para un backend de compilador diferente. La penalización del 18% de Val viene de su alias analysis más conservador; el compilador no pudo probar que ciertas operaciones de punteros en la cola lock-free no aliaseaban.

## Recomendaciones Prácticas

Para organizaciones con una codebase C++ grande existente y presión regulatoria para mejorar memory safety:

1. **Circle** para el camino más rápido a safety annotations sobre código existente, para quienes puedan aceptar el riesgo de mantenedor único.
2. **Cpp2** para la apuesta organizacional más segura, la salida es C++ estándar, de modo que siempre es posible "eyectar" conservando el código transpilado.
3. **Carbon** para quienes estén dispuestos a esperar 12-18 meses más para que el tooling madure y la codebase sea C++ moderno relativamente limpio.
4. **Val** solo para proyectos greenfield donde se busque el modelo de safety más elegante y no se necesite interop con C++.

Ninguno de estos es Rust. Rust tiene una década de ventaja en ecosistema, tooling y battle-testing en producción. Pero para shops donde 200 ingenieros saben C++ y reentrenar a Rust costaría dos años de velocidad, estos sucesores representan el camino pragmático del medio.

La migración desde C++ no será un proyecto de fin de semana. Ni siquiera va a ser un proyecto de un trimestre para cualquier codebase no trivial. Pero la presión regulatoria no se va a ir, y comenzar ahora, incluso con herramientas imperfectas, es preferible a esperar una solución perfecta que tal vez nunca llegue.

## Preguntas frecuentes

### ¿Carbon realmente puede interoperar con codebases C++ existentes?

Sí, la capa de interop de Carbon puede importar headers C++ directamente via \`import Cpp\`. Pero código muy templatizado, APIs basadas en SFINAE y headers llenos de macros todavía dan problemas. Código limpio C++17/20 moderno interopera mejor.

### ¿Cpp2/cppfront está listo para producción?

Todavía no para sistemas críticos. Cppfront transpila a C++20 válido, así que el output es tan production-ready como tu compilador de C++, pero el tooling, los mensajes de error y el soporte del ecosistema siguen madurando a principios de 2026.

### ¿Cómo se diferencia el modelo de ownership de Val del borrow checker de Rust?

Val usa semántica de valores mutables en lugar del enfoque basado en borrows de Rust. En vez de trackear referencias, Val asegura que cada valor tiene un único dueño y las mutaciones pasan in-place. Evita la experiencia de 'pelear contra el borrow checker' eliminando referencias mutables compartidas por completo.

## Tips

1. **Tip:** Evitar reescribir toda la codebase de una. Comenzar envolviendo los boundaries más peligrose es con un módulo del lenguaje sucesor y hacer interop de vuelta. La migración incremental es el único camino viable.
2. **Tip:** La directiva ` import Cpp ` de Carbon funciona mejor cuando los headers de C++ están limpios, sin macros-como-API, sin trucos SFINAE. Ejecutar ` clang-tidy ` con los modernize passes primero.
3. **Tip:** Al evaluar Val, comenzar con su semántica de ` mutableValue ` en un pipeline pequeño de procesamiento de datos. El modelo de ownership resulta más claro cuando se lo ve aplicado a transformaciones de grafos.

- c++
- carbon
- cpp2
- val
- circle
- memory-safety
- sistemas
- cppfront

[Volver al Blog](<https://lucioduran.com/es/blog>)
