Nim e Python, due linguaggi complementari (PyVenice #6)


Lo scorso 17 settembre, al PyVenice #6 a Verona ospitato al 311 Verona, ho tenuto un talk intitolato (volutamente un po’ clickbait) Nim e Python, con tanto di sondaggio iniziale: nessuno in sala conosceva Nim. In questo post provo a riassumerlo, con la stessa idea di fondo: non è un versus, ma il racconto di due linguaggi che si completano.

Il video completo della serata è su YouTube: PyVenice #6 a Verona. Slide e codice: codeberg.org/n1k9/py-venice-202606.

Non sono confrontabili (ed è proprio il punto)

Python è un linguaggio interpretato, uscito nel 1991. Nim è un linguaggio compilato, uscito nel 2008: più “maturo” di Rust (2012) o Zig (2016). Uno è pensato per un certo tipo di lavoro, l’altro per un tipo di lavoro diverso. Quello che li accomuna è la sintassi: Nim ha preso in prestito di proposito lo stile di Python per rendere friendly un linguaggio che, sotto, è a basso livello.

Quindi niente scontro: l’obiettivo è mostrare come si sovrappongono, e dove si integrano.

Compilato, non interpretato

Nim in realtà è transpilato: il compilatore traduce in C, C++, Objective-C o JavaScript, e poi quello viene compilato. Alla fine ottieni un eseguibile, senza dover distribuire un interprete.

Il vantaggio pratico:

  • performance vicine al C compilato — nei casi mostrati, indicativamente da 5 a 20 volte più veloce di Python;
  • distribuzione semplice: dai un binario e funziona, senza pip install né dipendenze a runtime.

Il tutto mantenendo un garbage collector (con la possibilità di disabilitarlo e gestire la memoria a mano, utile per esempio nei videogiochi dove non puoi permetterti pause di allocazione).

Stessa forma, regole diverse

A prima occhiata il codice Python e quello Nim si somigliano molto, ma le differenze saltano fuori presto:

  • Tipi ovunque: Nim è fortemente tipizzato, parametri e valori di ritorno vanno dichiarati (o inferiti dal compilatore);
  • let, var, const: si dichiarano esplicitamente le variabili. let è immutabile, var no, const è una sostituzione fatta in compilazione (come in C);
  • niente return obbligatorio: vale l’ultima espressione, oppure si usa la variabile implicita result, che funziona anche da accumulatore (a differenza di return, che esce subito);
  • if speculare: in Nim l’if è un’espressione, quindi l’operatore ternario è la stessa cosa dell’if;
  • scope dei blocchi: una variabile ridichiarata dentro un if vive solo lì; usciti dal blocco torna quella di prima (in Python no).

Collezioni, funzioni e severità

Le liste di Python diventano in Nim le sequenze (seq), con una differenza importante: gli elementi devono essere tutti dello stesso tipo. È una scelta di performance — se il compilatore sa quanto occupa ogni elemento, accede molto più velocemente. Esistono anche gli array a lunghezza fissa, che in Python non ci sono.

Non ci sono le list comprehension né le f-string “native”, ma ci sono equivalenti: filter/map con funzioni anonime (i lambda, che in Nim si scrivono con func/proc senza nome) e il modulo fmt per l’interpolazione delle stringhe.

Sulle funzioni pure (func, senza effetti collaterali, in stile matematico) il compilatore può calcolare il risultato già in fase di compilazione. E sui tipi “nominali” Nim è severo: due int di domini diversi restano tipi diversi e non si possono mescolare — niente “pere e mele” sommate per sbaglio.

Un’altra differenza che spiazza chi viene da Python è la verità: niente valori truthy/falsy. Una lista vuota, lo zero o nil non sono “falsi”: devi confrontare esplicitamente. È più scomodo, ma aiuta a non sbagliare.

Metaprogrammazione: template e macro

Una delle parti più interessanti: in Nim puoi definire nuovi costrutti sintattici, cosa impossibile in Python.

  • I template sono sostituzioni fatte in compilazione (come le const): il codice viene copiato dove lo chiami, senza l’overhead di una chiamata di funzione, dello stack e del passaggio parametri. Perfetto per cose come un logger.
  • Le macro vanno oltre: modificano l’albero sintattico vero e proprio, generando codice su misura.

Il risultato è un eseguibile potenzialmente più lungo, ma molto più veloce in esecuzione — e noi non ce ne accorgiamo, perché vediamo solo il binario finale.

Niente classi, ma qualcosa di simile

Nim non ha oggetti nel senso della programmazione a oggetti: niente classi, niente ereditarietà. Ha object (simili a struct) e ref object (referenziati in memoria e modificabili), e degli enum.

Però c’è la uniform call syntax: una normale funzione il cui primo parametro è l’oggetto può essere chiamata come se fosse un metodo di quell’oggetto. È zucchero sintattico, ma permette di comporre chiamate in cascata e di scrivere codice che assomiglia molto al Python “a oggetti”. Si possono anche ridefinire gli operatori (+, ==, …) per i propri tipi.

E il punto vero: usarli insieme

Qui sta il cuore del discorso. Nim ha un ecosistema piccolo, è vero: niente Jupyter come si intende in Python, comunità ridotta. Ma ha un asso nella manica: l’interoperabilità con il C/C++/Objective-C. Puoi chiamare praticamente qualsiasi libreria scritta in quegli ecosistemi.

E non solo: puoi far parlare Nim e Python tra loro.

  • Da Nim puoi importare Python (tramite librerie come nimpy) e usare tutto ciò in cui Python eccelle: dati, AI, librerie.
  • Dal lato opposto, puoi compilare una funzione Nim (o C) come libreria condivisa e importarla in Python: i testi veloci e critici si scrivono in Nim, e Python li richiama in modo trasparente.

Hai un collo di bottiglia? Lo isoli, lo riscrivi in Nim, e lo usi da Python senza cambiare il resto del progetto. È la stessa strategia che altri adottano con Rust: pezzi caldi in un linguaggio compilato, il resto in Python.

Dove ha senso Nim (e dove no)

Con la possibilità di disabilitare il GC, Nim è comodo per videogiochi, per networking, per tool da riga di comando che vuoi distribuire come singolo eseguibile, per il frontend web (compila in JS) e soprattutto per l’embedded: viene usato per firmware di microcontrollori, un po’ come MicroPython.

Il cross-compiling e gli eseguibili autonomi lo rendono ideale quando non vuoi chiedere all’utente di installare un interprete. E se un domani cambiassi idea, non perdi nulla: il codice transpilato in C lo compili dove vuoi.

I limiti restano quelli di un linguaggio piccolo: meno librerie “pronte”, meno documentazione rispetto a Python. Ma con l’interoperabilità C e un package manager come Nimble, quel che manca spesso te lo costruisci — o lo prendi in prestito.

Il resto della serata

A seguire, Alessandra Bilardi ha confrontato performance e comportamento di assistenti AI open e closed: in un ecosistema sempre più affollato, quale scegliere e per quale scopo. Anche quella parte della serata merita una visione.

In conclusione

Nim non sostituisce Python, e non è nato per farlo. È un linguaggio stabile, con una curva di apprendimento dolcissima per chi viene da Python, che eccelle dove serve velocità e un binario autonomo. La cosa più interessante è usarli insieme: Python resta il posto giusto per orchestratare, Nim entra in gioco dove le performance contano davvero.

Se il tema vi incuriosisce, il video integrale è online e slide e codice sono su Codeberg. Grazie a PyVenice, a Python Italia, al 311 Verona e a chi è passato a sentirlo dal vivo.