# Entendiendo las diferencias entre Rust, C++ moderno y ANSI C con estructuras de datos recursivas
Cuando aprendemos un nuevo lenguaje de programación, implementar una estructura de datos conocida, como un **árbol de expresiones matemáticas**, nos ayuda a entender cómo el lenguaje maneja la memoria, la seguridad y la ergonomía. En este documento, compararemos cómo Rust, C++ moderno y ANSI C abordan este problema clásico para evaluar expresiones como `(10 * 9) + ((3 - 4) * 5)`.

---
## 1. Rust: Seguridad y Ergonomía por Defecto 🦀
Rust está diseñado para prevenir errores desde la compilación, lo que lo hace ideal para estructuras recursivas.
### Tipos de Datos
Usamos **enums** para definir una expresión, que puede ser un valor o una operación. Para la recursividad, `Box<T>` almacena datos en el heap, evitando problemas de tamaño infinito.
```rust
/// Una operación a realizar.
enum Operation {
Add, Sub, Mul, Div,
}
/// Una expresión, en forma de árbol.
enum Expression {
/// Una operación sobre dos subexpresiones.
Op { op: Operation, left: Box<Expression>, right: Box<Expression> },
/// Un valor literal.
Value(i64),
}
```
### Lógica de Evaluación
Con **match**, Rust ofrece un "pattern matching" exhaustivo que obliga a cubrir todos los casos, eliminando errores.
```rust
fn eval(e: Expression) -> i64 {
match e {
Expression::Value(val) => val,
Expression::Op { op, left, right } => {
let left_val = eval(*left);
let right_val = eval(*right);
match op {
Operation::Add => left_val + right_val,
Operation::Sub => left_val - right_val,
Operation::Mul => left_val * right_val,
Operation::Div => left_val / right_val,
}
}
}
}
```
#### Conclusión
Rust combina seguridad y expresividad con enums, `Box<T>` y `match`, guiando al programador hacia un código libre de errores comunes.
---
## 2. C++ Moderno: Poder y Flexibilidad con Abstracciones ⚙️
C++ moderno (C++17+) ofrece herramientas como `std::variant` y `std::unique_ptr`, acercándose a la seguridad de Rust, pero con más responsabilidad para el programador.
### Tipos de Datos
`std::variant` actúa como un tipo suma seguro, mientras que `std::unique_ptr` gestiona la memoria automáticamente, similar a `Box<T>`.
```cpp
#include <variant>
#include <memory>
enum class Operation { Add, Sub, Mul, Div };
struct Expression; // Declaración previa
struct Op {
Operation op;
std::unique_ptr<Expression> left;
std::unique_ptr<Expression> right;
};
struct Value {
long long val;
};
struct Expression {
std::variant<Value, Op> node;
};
```
### Lógica de Evaluación
`std::visit` permite operar sobre la variante, aunque es más verboso que el `match` de Rust.
```cpp
long long eval(const Expression& e) {
return std::visit(
[](auto&& arg) -> long long {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Value>) {
return arg.val;
} else if constexpr (std::is_same_v<T, Op>) {
long long left_val = eval(*arg.left);
long long right_val = eval(*arg.right);
switch (arg.op) {
case Operation::Add: return left_val + right_val;
case Operation::Sub: return left_val - right_val;
case Operation::Mul: return left_val * right_val;
case Operation::Div: return left_val / right_val;
}
}
return 0; // No debería ocurrir
},
e.node);
}
```
#### Conclusión
C++ ofrece flexibilidad y seguridad con `std::variant` y `std::unique_ptr`, pero el programador debe usar estas herramientas conscientemente.
---
## 3. ANSI C: Control Total y Riesgo Máximo ☠️
En ANSI C, no hay abstracciones automáticas. El programador tiene control total, pero también toda la responsabilidad.
### Tipos de Datos
Usamos una **unión etiquetada** (tagged union) con un enum y una union. La recursividad se maneja con punteros crudos (`*`), y la memoria se gestiona manualmente con `malloc` y `free`.
```c
#include <stdlib.h>
typedef enum { TYPE_VALUE, TYPE_OP } ExpressionType;
typedef enum { OP_ADD, OP_SUB, OP_MUL, OP_DIV } Operation;
typedef struct Expression Expression; // Declaración previa
typedef struct {
Operation op;
Expression* left;
Expression* right;
} OpNode;
struct Expression {
ExpressionType type;
union {
long long value;
OpNode op;
} data;
};
```
### Lógica de Evaluación
Un `switch` actúa como "pattern matching". La gestión de memoria requiere una función `free_expression` para evitar fugas.
```c
long long eval(const Expression* e) {
switch (e->type) {
case TYPE_VALUE:
return e->data.value;
case TYPE_OP: {
long long left_val = eval(e->data.op.left);
long long right_val = eval(e->data.op.right);
switch (e->data.op.op) {
case OP_ADD: return left_val + right_val;
case OP_SUB: return left_val - right_val;
case OP_MUL: return left_val * right_val;
case OP_DIV: return left_val / right_val;
}
}
}
return 0; // No debería ocurrir
}
void free_expression(Expression* e) {
if (e && e->type == TYPE_OP) {
free_expression(e->data.op.left);
free_expression(e->data.op.right);
}
free(e);
}
int main() {
// Construcción del árbol: (10 * 9) + ((3 - 4) * 5)
Expression* term1 = create_op(OP_MUL, create_value(10), create_value(9));
Expression* sub_term = create_op(OP_SUB, create_value(3), create_value(4));
Expression* term2 = create_op(OP_MUL, sub_term, create_value(5));
Expression* root = create_op(OP_ADD, term1, term2);
printf("El resultado es: %lld\n", eval(root)); // Imprime 85
// Liberar la memoria del árbol
free_expression(root);
return 0;
}
```
#### Conclusión
C ofrece control y eficiencia, pero la gestión manual de memoria y la falta de seguridad lo hacen propenso a errores.
---
## Conclusión General
- **Rust**: Seguridad y ergonomía con enums y `Box<T>`.
- **C++ moderno**: Flexibilidad con `std::variant` y `std::unique_ptr`, pero requiere cuidado.
- **ANSI C**: Control total con punteros y gestión manual, a costa de seguridad.
Estos enfoques reflejan las filosofías de cada lenguaje, útiles según el contexto del proyecto.