Lezione 2 di 7 · 15 min di lettura

Hello, World!

Il primo programma, smontato riga per riga. Lo eseguiamo, lo compiliamo in un eseguibile nativo e poi lo rompiamo apposta, per conoscere il compilatore.

Il rito di iniziazione

Entra nella cartella del laboratorio, attiva l’ambiente (se non l’hai già fatto in questo terminale) e crea con l’editor un file hello.mojo:

hello.mojo
def main():
    print("Hello, World!")

Due righe. Eseguilo:

terminale
mojo hello.mojo
output
Hello, World!

Nessun progetto da generare, nessun file di configurazione per il singolo programma: un file .mojo con una funzione main è già un programma completo. Ora smontiamolo.

def main():

mojo
def main():

def dichiara una funzione, esattamente come in Python. main è il suo nome, le parentesi tonde contengono i parametri (qui nessuno) e i due punti aprono il corpo della funzione.

Il nome main è speciale: è il punto d’ingresso. Quando esegui un file, Mojo cerca una funzione main e comincia da lì. Ogni programma eseguibile ne ha esattamente una.

Il corpo è tutto ciò che sta sotto, indentato. Come in Python, sono i rientri a dire dove inizia e finisce un blocco: niente parentesi graffe. La convenzione (e lo stile del formattatore ufficiale) è di 4 spazi per livello.

print(“Hello, World!“)

mojo
    print("Hello, World!")

print è una funzione built-in: fa parte della libreria standard ed è sempre disponibile, senza bisogno di import. Scrive i suoi argomenti sullo standard output e va a capo. Il testo tra virgolette è un letterale stringa; Mojo accetta indifferentemente virgolette doppie "..." e singole '...', e il formattatore preferisce le doppie.

A differenza di molti linguaggi compilati, print accetta qualsiasi numero di argomenti di qualsiasi tipo “stampabile”, e li separa con uno spazio:

answer.mojo
def main():
    print("The answer is", 42)
    print("Pi is about", 3.14, "and that is", True)
output
The answer is 42
Pi is about 3.14 and that is True

Niente punti e virgola alla fine delle righe: ogni istruzione finisce con l’a capo.

Dettagli nerd Dove scrive print, esattamente? (stdout, stderr e buffer)

Ogni programma nasce con tre canali già aperti dal sistema operativo: lo standard input (da cui legge, di solito la tastiera), lo standard output (dove scrive, di solito il terminale) e lo standard error, riservato ai messaggi di errore. print scrive sullo standard output; vedremo più avanti come scrivere sullo standard error.

Scrivere davvero sul terminale significa chiedere un servizio al sistema operativo (una system call), un’operazione costosa rispetto ai calcoli. Per questo l’output passa spesso da un buffer: una zona di memoria dove i caratteri si accumulano per essere spediti in blocco. print ha anche un parametro flush=True per forzare la spedizione immediata, utile quando un programma lento deve mostrare i progressi in tempo reale.

Cosa fa davvero mojo hello.mojo

mojo hello.mojo è la forma breve di mojo run hello.mojo, e non interpreta il file: lo compila per intero in codice macchina, poi lo esegue subito. In ordine:

  1. Analisi: legge il file, controlla sintassi, nomi e tipi. Se qualcosa non va, si ferma qui con un errore e non esegue niente.
  2. Compilazione: traduce il programma nella rappresentazione intermedia (MLIR, poi LLVM), la ottimizza e ne ricava istruzioni per il tuo processore.
  3. Esecuzione: lancia il codice appena generato.

Per questo mojo hello.mojo impiega circa mezzo secondo anche per un programma di due righe: quasi tutto è tempo di compilazione, ripetuto a ogni avvio. Quando il programma è pronto, conviene compilarlo una volta sola in un eseguibile:

terminale
mojo build hello.mojo
./hello
output
Hello, World!

mojo build ha creato un file hello, un eseguibile nativo di circa 18 KB: puoi lanciarlo quante volte vuoi, parte in pochi millisecondi e non ricompila niente. Con -o scegli il nome del file: mojo build hello.mojo -o greet.

Dettagli nerd Posso copiare l'eseguibile su un altro computer?

Non ancora così com’è. Su Linux l’eseguibile prodotto da mojo build è un file ELF che, oltre alla libreria C di sistema, si appoggia a un paio di librerie condivise del runtime di Mojo (file .so dentro la .venv). Puoi vederle con ldd hello. Sul tuo computer le trova da solo, perché il loro percorso è scritto dentro l’eseguibile; su un’altra macchina servirebbe anche Mojo, o quelle librerie. Inoltre il codice è ottimizzato per il tuo processore: mojo build sceglie di default le istruzioni della CPU su cui compili (lo vedi con mojo build --print-effective-target hello.mojo).

Quiz

Hai modificato hello.mojo dopo aver lanciato mojo build hello.mojo. Cosa stampa ./hello?

Rompiamolo apposta

Il modo più rapido per conoscere un compilatore è farlo arrabbiare. Togli la parentesi tonda chiusa:

hello.mojo
def main():
    print("Hello, World!"
output
/home/ada/learn-mojo/hello.mojo:2:26: error: expected ')' in call argument list
    print("Hello, World!"
                         ^
/home/ada/learn-mojo/.venv/bin/mojo: error: failed to parse the provided Mojo source module

Il formato è sempre lo stesso: file, riga, colonna, poi la parola error: e la spiegazione. Sotto c’è la riga incriminata, con ^ sotto il punto esatto. L’ultima riga la scrive il comando mojo per dirti che si è fermato: il programma non è stato eseguito. (Nei tuoi messaggi vedrai il percorso vero della tua cartella al posto di /home/ada.)

Ora rimetti la parentesi e sbaglia il nome della funzione:

hello.mojo
def main():
    prnt("Hello, World!")
output
/home/ada/learn-mojo/hello.mojo:2:5: error: use of unknown declaration 'prnt'
    prnt("Hello, World!")
    ^~~~
/home/ada/learn-mojo/.venv/bin/mojo: error: failed to parse the provided Mojo source module

In Python questo errore esploderebbe solo quando l’esecuzione arriva a quella riga: magari dopo ore, magari mai durante i test. In Mojo il programma non parte proprio. È il primo regalo della compilazione.

Poi prova a sbagliare l’indentazione:

hello.mojo
def main():
    print("First")
      print("Second")
output
/home/ada/learn-mojo/hello.mojo:3:7: error: statement indentation must match the rest of the block; adjust to align
      print("Second")
      ^
/home/ada/learn-mojo/.venv/bin/mojo: error: failed to parse the provided Mojo source module

Infine, prova a scrivere codice fuori da main:

hello.mojo
print("Outside")


def main():
    print("Inside")
output
/home/ada/learn-mojo/hello.mojo:1:1: error: expressions must not appear at file scope; move this into a function body
print("Outside")
^
/home/ada/learn-mojo/.venv/bin/mojo: error: failed to parse the provided Mojo source module

Nota come il compilatore non si limita a dire cosa non va: ti dice anche cosa fare (“move this into a function body”, “adjust to align”). Ci farai l’abitudine.

Quiz

Rinomini main in start e lanci mojo hello.mojo. Cosa succede?

Più righe, più stampe

Le istruzioni del corpo vengono eseguite in ordine, dall’alto in basso. print accetta anche due argomenti “con nome” che cambiano il suo comportamento: end (cosa stampare alla fine, di default un a capo) e sep (cosa mettere tra un argomento e l’altro, di default uno spazio):

hello.mojo
def main():
    print("First line")
    print("Second line")
    print("Third", end="")
    print(" line")
    print(2026, 9, 27, sep="-")
    print()
    print("After an empty line")
output
First line
Second line
Third line
2026-9-27

After an empty line

end="" evita l’a capo, così la stampa successiva continua sulla stessa riga; print() senza argomenti stampa solo l’a capo, cioè una riga vuota.

Esercizio · sul tuo computer

Presentati alla macchina

Nella cartella learn-mojo crea un file greeting.mojo che stampi esattamente queste due righe:

output
Hello, Mojo!
I'm ready.

Eseguilo con mojo greeting.mojo e copia qui sotto le righe stampate. Attenzione all’apostrofo di I'm: con le virgolette doppie non dà nessun problema.

Mostra una soluzione (prima prova da solo!)
greeting.mojo
def main():
    print("Hello, Mojo!")
    print("I'm ready.")

Commenti e docstring

I commenti iniziano con # e durano fino alla fine della riga; il compilatore li ignora. Per documentare una funzione (o un file intero) si usano invece le docstring: una stringa tra tre virgolette, come prima riga del corpo o del file.

hello.mojo
"""A tiny program that greets the world."""


def main():
    """Entry point: prints a greeting."""
    # An ordinary comment, ignored by the compiler.
    print("Hello")  # A comment can also follow some code.
output
Hello

Le docstring non sono solo per chi legge il sorgente: l’estensione dell’editor te le mostra quando passi sopra un nome, e il comando mojo doc hello.mojo le raccoglie in un file JSON da cui si genera la documentazione (è così che nasce il sito della libreria standard). Non esistono commenti su più righe in stile /* ... */: per un commento lungo si ripete # a ogni riga.

Il formattatore vuole due righe vuote tra una definizione e l’altra al livello più esterno del file, e due spazi prima di un # in fondo a una riga: sono le stesse regole di Black, il formattatore più usato in Python, da cui quello di Mojo deriva. Lo useremo nella prossima lezione.

Ricapitolando

  • Un programma Mojo è un file .mojo con una funzione def main():, il punto d’ingresso.
  • I blocchi si aprono con i due punti e si delimitano con l’indentazione (4 spazi); niente punti e virgola.
  • Il codice eseguibile sta sempre dentro una funzione: niente istruzioni a livello di file.
  • print(...) stampa i suoi argomenti separati da spazi e va a capo; sep e end cambiano separatore e finale.
  • mojo file.mojo compila ed esegue ogni volta; mojo build file.mojo produce un eseguibile nativo da lanciare con ./file.
  • Gli errori indicano file, riga e colonna, e spesso suggeriscono la correzione.
  • Commenti con #, documentazione con le docstring """...""".

Nella prossima lezione guardiamo dentro il laboratorio: cosa sono i file che uv ha creato, come si aggiorna Mojo, e quali altri comandi mette a disposizione mojo.