sábado, 27 de diciembre de 2025

Ballerina: una comparativa con Rust

Como mencionamos en la entrega anterior, Ballerina es un lenguaje de programación en crecimiento. No posee una comunidad tan grande como otros lenguajes (Java, C#, C/C++, Python, etc.), pero así empiezan todos. Ballerina junto otros lenguajes como Go y Rust han cobrado cierto interés en el mundo de la programación, servicios Cloud, servicios REST y hasta la I.A.

En esta ocasión haremos una comparativa con Rust, un lenguaje de programación que tiene inspiración en lenguajes como C y C++, tomando lo mejor de ellos y descartando lo que no es necesario y Ballerina, un lenguaje que ha cobrado importancia en el desarrollo en la nube. Ambos lenguajes han aprendido de los mejores e incluso han ido superándolos incluso en velocidad y rendimiento.

Ballerina y Rust son lenguajes muy distintos: Ballerina está orientado a integración y microservicios, mientras que Rust se centra en sistemas de alto rendimiento y seguridad de memoria.

Veamos una tabla comparativa:

Comparativa: Ballerina vs Rust
Aspecto Ballerina Rust
Paradigmas Multiparadigma con foco en integración, concurrencia y servicios distribuidos; código con visualización de flujos y llamadas. Multiparadigma (imperativo y funcional) orientado a sistemas, control de bajo nivel y seguridad de memoria.
Casos de uso Integración de APIs, microservicios, orquestación de endpoints, aplicaciones cloud-native y seguridad en comunicaciones. Sistemas operativos, motores de juegos, programación embebida, CLI y servicios de alto rendimiento.
Compilación y ejecución Compila con bal build; ejecuta con bal run archivo.bal. Soporte integrado para Docker/Kubernetes. Compila con rustc archivo.rs; ejecuta con ./archivo. En proyectos se usa Cargo (cargo build, cargo run).
Tipos de variables Tipado estático: int, float, boolean, string; estructurados: record, map, array, table. Tipado estático con inferencia: primitivos (i32, f64, bool, char) y avanzados (struct, enum, tuple).
Estructuras de control if, while, foreach, match; concurrencia con workers y fork/join. if, loop, while, for, match con patrones; concurrencia segura con async/await y ownership.
Estructuras de datos record (tipo estructurado), map, array, table. struct, enum, tuple, array, Vec<T>, HashMap<K,V>.
Concurrencia Modelo de trabajadores, transacciones y flujos de integración; primitivas para servicios distribuidos. Seguridad de datos en concurrencia mediante ownership, borrowing y tipos como Arc/Mutex.
Ecosistema Enfocado en integración (HTTP, gRPC, GraphQL, Kafka) con librerías estándar orientadas a servicios. Amplio ecosistema de crates para sistemas, redes, WebAssembly, embebidos y desarrollo multiplataforma.
Curva de aprendizaje Suave para integraciones y microservicios; diseño opinado para flujos de red. Pronunciada por el ownership y el borrow checker; recompensa con rendimiento y seguridad.

Puntos clave entre Ballerina y Rust

Ambos lenguajes tienen similitudes y diferencias, aquí los puntos claves:

  • Enfoque distinto: Ballerina es un lenguaje de integración; Rust es un lenguaje de sistemas. 
  • Compilación: Rust ofrece control de bajo nivel y optimización; Ballerina simplifica despliegues en entornos distribuidos. 
  • Datos y control: Rust tiene estructuras más ricas y flexibles; Ballerina prioriza simplicidad y seguridad en datos de red. 
  • Concurrencia: Ambos soportan concurrencia, pero Rust lo hace con un modelo de ownership y Ballerina con workers y diagramas visuales.

Ejemplo 1. Crear un sencillo "Hola, mundo" en Ballerina y Rust. Esto nos servirá para ver cómo se compila y ejecuta un programa en cada lenguaje.

Empecemos con Ballerina. Compilaremos y ejecutaremos un archivo llamado holamundo.bal:

import ballerina/io;

public function main(string... args) {
    io:println("\t Hola, mundo en Ballerina");
}

Compilamos y ejecutamos:

$ bal run holamundo.bal

El código se compilará y se ejecutará en la terminal, la salida será:

Compiling source
        holamundo.bal

Running executable

         Hola, mundo en Ballerina

Ahora con Rust, compilaremos y ejecutaremos el archivo holamundo.rs:

fn main() {
    println!("\t Hola mundo en Rust");
}

Compilamos:

$ rustc holamundo.rs

Se creará un ejecutable:

$ holamundo.exe

Salida:

         Hola mundo en Rust

¿Qué podemos notar?

  • Ambos lenguajes son compilados. 
  • Ballerina es más similar Java, es dependiente de la JVM y genera bytecodes
  • Rust es más similar a C/C++, genera ejecutables nativos (*exe). 
  • La extensión de un programa Ballerina es *bal.
  • La extensión de un programa Rust es *rs.

Tipos de datos en Ballerina y Rust

Como mencioanmos, ambos lenguajes son compilados. También comparten la característica de ser de tipado estático. Eso quiere decir que debes especificar el tipo de datos a usar. No como lenguajes como Python o Ruby donde "una variable es una variable".

Tipos de datos en Ballerina vs Rust
Categoría Ballerina Rust
Primitivos numéricos int, float, decimal i8, i16, i32, i64, u8, u16, u32, u64, f32, f64
Booleanos boolean bool
Texto y caracteres string char, String (tipo de colección)
Compuestos record, map, array, table struct, enum, tuple, array, Vec<T>, HashMap<K,V>
Especiales nil (valor nulo), error, any Option<T>, Result<T,E>, unit (())
Concurrencia Workers y canales integrados para paso de mensajes Tipos seguros para concurrencia: Arc, Mutex, RwLock

Ejemplo 2. Crear un programa en ambos lenguajes con el objetivo de ver los tipos de datos en cada lenguaje.

En Ballerina:

tipos.bal

import ballerina/io;

type Persona record {|
    string nombre;
    int edad;
|};

enum Estado {
    ACTIVO,
    INACTIVO
}

public function main() {
    int edad = 30;
    float altura = 1.75;
    boolean activo = true;
    string inicial = "C"; // no existe 'char'
    string nombre = "Thomas Muller.";

    int[] numeros = [1, 2, 3, 4];
    [int, float, string] tupla = [25, 1.80, "Ana"];

    Persona p = { nombre: "Luis", edad: 40 };
    Estado estado = ACTIVO;

    map<string> capitales = { "MX": "Ciudad de México", "US": "Washington" };

    int|error resultado = 10; // Result
    float? opcion = 3.14;     // Option

    
    int contador = 0;
    fork {
        worker w1 {
            int local = contador;
            local += 1;
            io:println("Worker 1 contador: " , local.toString());
        }
        worker w2 {
            int local = contador;
            local += 1;
            io:println("Worker 2 contador: " , local.toString());
        }
    }

	io:println("Nombre: " + nombre + ", Edad: " + edad.toString());
    string capitalMX = capitales["MX"] ?: "Desconocido";
    io:println("Capital de MX: " + capitalMX);
    io:println("Persona: " + p.toString());
    io:println("Contador final (simulado): " + contador.toString());
}

Compilamos y ejecutamos:

$ bal run tipos.bal

Salida:

Nombre: Thomas Muller., Edad: 30
Capital de MX: Ciudad de México
Persona: {"nombre":"Luis","edad":40}
Contador final (simulado): 0
Worker 2 contador: 1
Worker 1 contador: 1

En Rust:

tipos.rs

fn main() {
    
    let edad: i32 = 30;
    let altura: f64 = 1.75;
    let activo: bool = true;
    let inicial: char = 'C';
    let nombre: String = String::from("Thomas Muller.");

    
    let numeros: [i32; 4] = [1, 2, 3, 4];
    let tupla: (i32, f64, &str) = (25, 1.80, "Ana");

    struct Persona {
        nombre: String,
        edad: i32,
    }
    let p = Persona { nombre: String::from("Luis"), edad: 40 };

    enum Estado {
        Activo,
        Inactivo,
    }
    let estado = Estado::Activo;

    use std::collections::HashMap;
    let mut capitales = HashMap::new();
    capitales.insert("MX", "Ciudad de México");
    capitales.insert("US", "Washington");

    let resultado: Result<i32, &str> = Ok(10);
    let opcion: Option<f64> = Some(3.14);

   
    use std::sync::{Arc, Mutex};
    use std::thread;

    let contador = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..2 {
        let contador = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            let mut num = contador.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for h in handles {
        h.join().unwrap();
    }

    println!("Nombre: {}, Edad: {}", nombre, edad);
    println!("Capital de MX: {}", capitales["MX"]);
    println!("Persona: {} ({})", p.nombre, p.edad);
    println!("Contador final: {}", *contador.lock().unwrap());
}

Compilamos:

$ rustc tipos.rs

Se creará un ejecutable:

$ tipos.exe

Salida:

Nombre: Thomas Muller., Edad: 30
Capital de MX: Ciudad de México
Persona: Luis (40)
Contador final: 2

Podríamos continuar con las comparaciones con estructuras de datos, acceso a archivos, acceso a base de datos y hasta servicios REST. Sin embargo, lo haremos en otra ocasión.

Conclusiones

Es importante hacer notar que:

  • Rust: curva de aprendizaje más pronunciada por su sistema de ownership y borrow checker
  • Ballerina: menos comunidad y ecosistema que Rust; puede ser limitante fuera del ámbito de integración.

Ambos lenguajes tienen un propósito, resuelven problemas distintos. No son competidores.

Enlaces:

https://rust-lang.org/
https://ballerina.io/
https://www.infoq.com/articles/ballerina-integration-tutorial-part-2/
https://www.infoq.com/articles/ballerina-microservices-language-part-1/



No hay comentarios:

Publicar un comentario

Ballerina: una comparativa con Rust

Como mencionamos en la entrega anterior , Ballerina es un lenguaje de programación en crecimiento. No posee una comunidad tan grande com...

Etiquetas

Archivo del blog