Saltar a contenido

Anexo · Mojo: Python con velocidad de sistemas

Anexo informativo. Mojo no forma parte de las dependencias del curso ni se instala con el proyecto; esta página es introductoria, para orientarte y comparar lenguajes. Los ejemplos proceden de la documentación oficial y necesitan el SDK de Mojo para ejecutarse.

Qué es Mojo

Mojo es un lenguaje de programación creado por Modular (fundada por Chris Lattner, creador de LLVM y Swift, junto a Tim Davis). Su propuesta es ambiciosa: combinar la facilidad de Python con el rendimiento de C/C++/Rust, sobre todo para inteligencia artificial y cálculo intensivo.

La idea central es que Mojo ofrece una sintaxis familiar e interoperabilidad con Python, pero no garantiza compatibilidad total: parte del código Python se ejecuta tal cual y otra parte necesita adaptaciones. Sobre esa base puedes añadir características de sistemas —tipos estáticos, estructuras struct y gestión de memoria segura— para hacer el programa más especializado.

Lo que hace especial a Mojo:

  • Rendimiento de nivel C/Rust en cómputo numérico y bucles tensos (compilado y optimizado en tiempo de compilación).
  • Sin GIL: programación concurrente de verdad.
  • Puerto a GPU y aceleradores de forma nativa.
  • Interop con Python: puede importar muchas librerías de Python usando el runtime de CPython, aunque la interoperabilidad no equivale a compatibilidad al 100 %.
  • Memoria segura con borrow checker (owned/borrowed).

Estado: ahora es open source

Hasta hace poco el compilador de Mojo era cerrado (solo la biblioteca estándar y parte del código de kernels eran abiertos). El 18 de agosto de 2026, tras alcanzar Mojo la versión 1.0 con estabilidad de código fuente, Modular abrió el compilador y las herramientas bajo licencia Apache 2.0 (con excepciones LLVM), en el repositorio de GitHub modular/modular (anuncio oficial).

Instalación con uv

Mojo se instala "como un paquete de Python o de Conda" en macOS y Linux. La documentación oficial admite uv y pixi como gestores equivalentes; aquí usamos uv, el del curso. Primero, instala uv si no lo tienes:

curl -LsSf https://astral.sh/uv/install.sh | sh

Instalación en un proyecto:

uv init hello-mojo
cd hello-mojo
uv add mojo

Windows

En Windows, Mojo se usa vía WSL2 (subsistema de Linux), no de forma nativa. Si quieres el build nightly (más nuevo, con posibles bugs), añade el índice: uv add mojo --index https://whl.modular.com/nightly/simple/ --prerelease allow. Modular ha anunciado cambios de ubicación de ese índice; si falla, consulta la guía de instalación oficial.

Tu primer programa

Crea hola.mojo:

def main():
    print("Hola, Mojo")

Ejecuta:

uv run mojo hola.mojo

Salida esperada:

Hola, Mojo

Todo programa Mojo usa main() como punto de entrada. También puedes pedir datos al usuario (input() es built-in) y compilar a un ejecutable nativo:

def main() raises:
    var nombre: String = input("¿Quién eres? ")
    print("Hola,", nombre)
uv run mojo hola.mojo

Salida esperada (escribiendo Edna):

¿Quién eres? Hola, Edna

Como input() puede fallar, main() lleva raises. Y si quieres un binario que no dependa del intérprete:

uv run mojo build hola.mojo   # genera ./hola
./hola
¿Quién eres? Hola, Edna

Compilado, no interpretado

Al ejecutar con mojo hola.mojo, Mojo compila just-in-time y luego corre el resultado; con mojo build obtienes un ejecutable independiente.

Un ejemplo guiado (del quickstart oficial)

Mojo se parece mucho a Python, pero introduce tipos, var y algunas diferencias de sintaxis. Un analizador de temperaturas:

def calcular_promedio(temps: List[Float64]) -> Float64:
    var total = 0.0
    for temp in temps:
        total += temp
    return total / Float64(len(temps))

def main():
    var temps: List[Float64] = [20.5, 22.3, 19.8, 25.1]
    print("Registradas", len(temps), "temperaturas")

    var media = calcular_promedio(temps)
    print("Media:", round(media, 2))

Salida esperada:

Registradas 4 temperaturas
Media: 21.92

Diferencias frente a Python que conviene ver aquí:

  • Usamos var para dejar explícita la intención de trabajar con una variable mutable; la documentación estable actual no debe presentarse como si let garantizara inmutabilidad. Mojo infiere el tipo o lo anotas (List[Float64]).
  • Las firmas de función indican tipos de parámetros y de retorno (-> Float64).
  • t"..." crea cadenas plantilla que interpolan sin asignaciones intermedias.
  • Float64(len(temps)) convierte el entero a Float64 para la división.

Declarar variables

En este anexo usamos var para las variables que pueden cambiar. El tipo se puede anotar o dejar que Mojo lo infiera:

def main():
    var nombre: String = "Mojo"
    var contador = 0
    print("Nombre:", nombre)   # valor inicial
    nombre = "Mojo 1.0"        # reasignar: `var` permite cambiar el valor
    contador += 1
    print("Nombre:", nombre)   # valor reasignado
    print("Contador:", contador)

Salida esperada:

Nombre: Mojo
Nombre: Mojo 1.0
Contador: 1

Colecciones: List tipada

Las colecciones de Mojo son homogéneas y tipadas en tiempo de compilación: List[Int] solo puede contener enteros, y el compilador lo comprueba. El [Int] entre corchetes es un parámetro de tipo (similar a list[int] en Python moderno, pero comprobado al compilar):

def main():
    var nums: List[Int] = [12, -7, 64]
    nums.append(-937)
    print("Numero de elementos:", len(nums))
    print("Ultimo elemento (pop):", nums.pop())
    print("Primero:", nums[0], "| Segundo:", nums[1])
    print("Ultimo:", nums[len(nums) - 1])

Salida esperada:

Numero de elementos: 4
Ultimo elemento (pop): -937
Primero: 12 | Segundo: -7
Ultimo: 64

Bucles con for y plantillas

El bucle for...in funciona como en Python, y las plantillas t"..." interpolan valores directamente en el texto:

def main():
    var dias: List[String] = ["Lunes", "Martes", "Miércoles"]
    for dia in dias:
        print(t"Día: {dia}")

Salida esperada:

Día: Lunes
Día: Martes
Día: Miércoles

Condicionales

if/elif/else también son casi idénticos a Python; la diferencia es que los valores numéricos y los tipos son estáticos:

def clasificar(media: Float64) -> String:
    if media > 25.0:
        return "Semana calurosa"
    elif media > 20.0:
        return "Semana templada"
    else:
        return "Semana fresca"

def main():
    var media = 21.92
    print(clasificar(media))

Salida esperada:

Semana templada

Un caso TechShop: totales y mejor pedido

Lo anterior, junto en un mini-caso del caso conductor: recorrer una lista de importes, acumular el total y localizar el máximo:

def main():
    var ventas: List[Float64] = [120.5, 98.3, 143.0, 87.25]
    var total = 0.0
    for venta in ventas:
        total += venta
    var media = total / Float64(len(ventas))
    print("Ventas:", len(ventas), "pedidos")
    print(t"Total: {round(total, 2)} € | Media: {round(media, 2)} €")

    var mejor = ventas[0]
    var indice = 0
    for i in range(len(ventas)):
        if ventas[i] > mejor:
            mejor = ventas[i]
            indice = i
    print(t"Mejor pedido: {mejor} € (dia {indice + 1})")

Salida esperada:

Ventas: 4 pedidos
Total: 449.05 € | Media: 112.26 €
Mejor pedido: 143.0 € (dia 3)

Fíjate en Float64(len(ventas)): len devuelve Int, y Mojo no mezcla Int con Float64 en una división sin que conviertas explícitamente. Es la misma idea que int()/float() en Python, pero comprobada al compilar.

Manejo de errores

Una función puede indicar que puede lanzar una excepción (raises) y se maneja con try/except:

def calcular_promedio(temps: List[Float64]) raises -> Float64:
    if len(temps) == 0:
        raise Error("No hay datos de temperatura")
    var total = 0.0
    for temp in temps:
        total += temp
    return total / Float64(len(temps))

def main():
    try:
        var media = calcular_promedio([])   # lista vacía
        print("Media:", media)
    except e:
        print("Error:", e)

Salida esperada:

Error: No hay datos de temperatura

Usar librerías de Python

Mojo importa módulos de Python tal cual:

from std.python import Python

def main() raises:
    var np = Python.import_module("numpy")
    print(np.mean([20.0, 22.0, 19.0, 25.0]))

Salida esperada:

21.5

Dos detalles de Mojo 1.0: la llamada puede fallar en tiempo de ejecución (NumPy podría no estar instalada), así que main() se marca raises; y las listas pasan a NumPy como Float64. Requiere tener numpy instalada en el entorno (uv add numpy).

Structs: crear tu propio tipo

El equivalente Mojo de una clase Python es el struct, y tiene reglas propias: los campos se declaran con var y su tipo, todo se inicializa en el constructor, y la estructura queda fijada al compilar (no puedes añadir campos al vuelo como en Python). El decorador @fieldwise_init genera el constructor campo a campo:

@fieldwise_init
struct Producto:
    var nombre: String
    var precio: Float64

    def con_iva(self) -> Float64:
        return self.precio * 1.21

def main():
    var raton = Producto(nombre="Raton inalambrico", precio=24.9)
    var monitor = Producto(nombre="Monitor 24", precio=189.0)

    print(raton.nombre, "-", raton.precio, "€")
    print(t"Con IVA: {round(raton.con_iva(), 2)} €")
    print(t"Monitor con IVA: {round(monitor.con_iva(), 2)} €")

Salida esperada:

Raton inalambrico - 24.9 €
Con IVA: 30.13 €
Monitor con IVA: 228.69 €

Para guardar structs en una List (o recorrerla), el tipo debe ser copiable: se declara con los rasgos Copyable y Movable (Mojo genera los constructores de copia y movimiento por ti). Es el equivalente a la distinción copia/referencia que viste en 1.6 · Listas y tuplas, aquí explícita y comprobada al compilar:

@fieldwise_init
struct Producto(Copyable, Movable):
    var nombre: String
    var precio: Float64

    def con_iva(self) -> Float64:
        return self.precio * 1.21

    def es_caro(self) -> Bool:
        return self.precio > 100.0

def main():
    var catalogo: List[Producto] = [
        Producto(nombre="Raton", precio=24.9),
        Producto(nombre="Teclado", precio=79.9),
        Producto(nombre="Monitor", precio=189.0),
    ]
    var total = 0.0
    for p in catalogo:
        print(p.nombre, "-", p.precio, "€ | caro:", p.es_caro())
        total += p.precio
    print("Total catalogo:", round(total, 2), "€")

Salida esperada:

Raton - 24.9 € | caro: False
Teclado - 79.9 € | caro: False
Monitor - 189.0 € | caro: True
Total catalogo: 293.8 €

Fíjate en dos diferencias con una clase Python: los métodos que modifican el struct deben declarar mut self (por defecto self es inmutable), y no hay herencia — la reutilización se consigue con traits, que quedan fuera de este anexo.

Comparación con Python "normal"

Aspecto Python Mojo
Ejecución Interpretado Compilado
Tipos Dinámicos Estáticos (con inferencia) + struct
Concurrencia Con GIL (limitada) Sin GIL (paralelismo real)
Rendimiento (cómputo numérico) Depende de las librerías Potencialmente alto en código compilado; hay que medir
Biblioteca estándar Enorme y madura Creciente (puede usar la de Python)
Gestión de memoria GC / refcount Owned/borrow (segura)
IA y GPU Vía librerías Nativo (aceleradores, kernels)
Curva de aprendizaje Baja Media-alta (conceptos de sistemas)
Madurez Muy maduro 1.0, en evolución rápida

A matizar

Mojo no siempre es más rápido que Python: depende de la carga. Destaca en cómputo numérico y bucles tensos; en ciertas operaciones de texto o I/O da resultados comparables (e incluso ha habido casos más lentos). Además es joven: no todo Python funciona tal cual y el ecosistema nativo aún crece. Considera Mojo como una herramienta a vigilar, no como sustitución de Python hoy.

¿Te interesa en este curso?

Para Programación de IA, Mojo es una lectura y experimentación interesante: refuerza la idea de que se puede partir de Python y bajar a sistemas (tipos, compilación, aceleradores) cuando el rendimiento importa. No sustituye a Python ni al material del curso: es un complemento para ampliar el horizonte y ver por qué el lenguaje elegido en el curso es Python.

Referencias