Lezione 2 di 7 · 15 min di lettura

Hello, World!

Il primo programma, smontato parola per parola. Poi lo compiliamo, lo lanciamo e lo rompiamo apposta.

Il rito di iniziazione

Ogni programmatore, dal 1978 a oggi, ha iniziato scrivendo un programma che stampa “Hello, World!“. Non c’è una legge, ma quasi. Ecco la versione C3:

hello.c3
import std::io;

fn void main()
{
	io::printn("Hello, World!");
}

Cinque righe. Salvale in un file chiamato hello.c3 nella cartella degli esercizi, poi nel terminale:

terminale
c3c compile-run hello.c3
output
Program linked to executable './hello'.
Launching ./hello
Hello, World!
Program completed with exit code 0.

Le righe che iniziano con Program e Launching le scrive il compilatore per dirti cosa sta facendo. Hello, World! l’hai scritta tu. Congratulazioni: hai fatto fare qualcosa a un computer.

Ora smontiamo il programma. Ogni parola ha un motivo.

Riga 1: import std::io;

c3
import std::io;

Un programma C3 parte “vuoto”: non sa nemmeno stampare sullo schermo. Le funzionalità sono organizzate in moduli, e import dice al compilatore quali vuoi usare. std::io è il modulo di input/output della libreria standard (std): dentro c’è tutto ciò che serve per leggere e scrivere testo.

I due punti doppi :: separano i livelli di un percorso: std è la libreria standard, io è un modulo al suo interno.

Riga 3: fn void main()

c3
fn void main()

Questa riga dichiara una funzione: un blocco di codice con un nome, che puoi far eseguire chiamandolo. Leggila da sinistra a destra:

PezzoSignificato
fn“Sto per definire una funzione.” Tutte le funzioni in C3 iniziano così.
voidIl tipo di ritorno: cosa la funzione restituisce a chi la chiama. void significa “niente”.
mainIl nome della funzione.
()La lista dei parametri, cioè dei dati che la funzione riceve. Qui è vuota.

Il nome main è speciale: è il punto d’ingresso. Quando lanci il programma, il sistema operativo cerca main e comincia da lì. Ogni programma ne ha esattamente una.

Dettagli nerd Dove va a finire il codice di uscita?

Al sistema operativo, che lo consegna a chi ha lanciato il programma: di solito la shell. Puoi leggerlo subito dopo l’esecuzione con echo $? (su Linux e macOS; in PowerShell è $LASTEXITCODE). Script e strumenti automatici lo usano per capire se un passaggio è andato bene: c3c compile hello.c3 && ./hello lancia il programma solo se la compilazione è terminata con 0. Anche c3c stesso, infatti, è un programma con il suo codice di uscita: 0 se compila, 1 se trova errori.

Righe 4 e 6: le graffe { e }

Le parentesi graffe delimitano il corpo della funzione: tutto ciò che sta in mezzo viene eseguito quando la funzione viene chiamata. In C3 le graffe sono obbligatorie, anche per una sola riga.

Dove metti la graffa aperta (sulla stessa riga o su quella dopo) è questione di gusto. La libreria standard di C3 la mette a capo, come nell’esempio. Entrambe queste versioni sono identiche per il compilatore:

c3
fn void main()
{
	io::printn("Allman style");
}

fn void other() {
	io::printn("K&R style");
}

Scegline uno e sii coerente.

Riga 5: io::printn("Hello, World!");

c3
	io::printn("Hello, World!");

Questa è una chiamata di funzione: chiediamo alla funzione printn del modulo io di fare il suo lavoro. Il nome completo sarebbe std::io::printn, ma C3 permette di usare solo l’ultimo pezzo del percorso del modulo, io. È la convenzione: corta e leggibile.

Il testo tra virgolette, "Hello, World!", è una stringa (una sequenza di caratteri) e viene passato alla funzione come argomento. printn la stampa e poi va a capo (la n finale sta per newline). Esiste anche io::print, identica ma senza andare a capo.

Dettagli nerd Dove scrive printn, esattamente? (e cos'è un 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). io::printn scrive sullo standard output.

Scrivere davvero sul terminale significa fare una chiamata di sistema, cioè chiedere un servizio al sistema operativo: un’operazione relativamente lenta. Per questo l’output passa spesso da un buffer: una zona di memoria usata come sala d’attesa, dove i caratteri si accumulano per essere spediti tutti insieme (per esempio quando il buffer è pieno o quando il programma finisce). È come aspettare di avere la lavatrice piena prima di avviarla: stesso risultato, molta meno fatica.

Il punto e virgola ; chiude l’istruzione. In C3 ogni istruzione finisce con ;. Dimenticarlo è l’errore numero uno dei principianti: il compilatore te lo farà notare con gentilezza teutonica.

Il rientro della riga (la tabulazione all’inizio) è solo per gli occhi umani: al compilatore non interessa. Ma ai tuoi occhi sì, quindi rientra sempre il codice dentro le graffe.

Quiz

Cosa succede se rinomini main in start?

Compilare: cosa succede davvero

Il comando c3c compile-run fa due cose in sequenza:

  1. Compila hello.c3 in un eseguibile chiamato hello (o hello.exe su Windows).
  2. Lancia l’eseguibile.

Puoi anche separarle:

terminale
c3c compile hello.c3   # crea l'eseguibile
./hello                # lo lancia (su Windows: hello.exe)

L’eseguibile è un file indipendente: puoi copiarlo su un altro computer con lo stesso sistema operativo e funzionerà anche senza c3c installato. È questo il bello dei linguaggi compilati.

Dettagli nerd Compilatore e linker: chi fa cosa?

Dietro a c3c compile ci sono due lavori distinti:

  1. Compilazione: ogni file .c3 viene tradotto in un file oggetto (.o su Linux e macOS, .obj su Windows). È già codice macchina, ma “a pezzi”: al posto delle funzioni definite altrove ci sono dei buchi. Il tuo hello.c3 chiama printn, che però vive nella libreria standard.
  2. Collegamento (linking): il linker prende tutti i file oggetto e le librerie, riempie i buchi collegando ogni chiamata alla funzione giusta e cuce il tutto in un unico eseguibile, pronto a partire da main.

Per questo il compilatore dice “Program linked to executable”: l’ultima cosa fatta è stata il collegamento. E per questo su macOS servono gli Xcode Command Line Tools: forniscono il linker di sistema.

Rompiamolo apposta

Il modo più rapido per capire un compilatore è farlo arrabbiare. Prova a togliere il punto e virgola:

broken.c3
import std::io;

fn void main()
{
	io::printn("Hello, World!")
}
output
 2:
 3: fn void main()
 4: {
 5:     io::printn("Hello, World!")
                                  ^
broken.c3:5:29: error: Expected ';'

Il compilatore ti mostra il pezzo di codice incriminato, con un ^ sotto il punto esatto, e poi la riga nel formato nome file : riga : colonna : messaggio. Riga 5, colonna 29, “mi aspettavo un ;”. Non serve altro.

Ora prova a togliere import std::io;: il compilatore ti dirà che non conosce nessun io. Ora prova a scrivere Io::printn con la maiuscola. Ogni errore che provochi adesso è un errore che riconoscerai al volo dopo.

Quiz

Quale di queste righe stampa hello e poi world sulla stessa riga?

Più righe, più stampe

Puoi mettere quante istruzioni vuoi dentro main. Vengono eseguite in ordine, dall’alto in basso:

three_lines.c3
import std::io;

fn void main()
{
	io::printn("First line");
	io::printn("Second line");
	io::print("Third");
	io::print(" line");
	io::printn("");
}
output
First line
Second line
Third line

Nota l’ultima io::printn(""): stampa una stringa vuota e va a capo. È un trucco comune per “chiudere” una riga costruita con più print.

Esercizio · sul tuo computer

Presentati alla macchina

Scrivi un programma greeting.c3 che stampi esattamente queste due righe:

output
Hello, C3!
I'm ready.

Compilalo con c3c compile-run greeting.c3, poi copia qui sotto le righe stampate dal programma (non quelle del compilatore).

Mostra una soluzione (prima prova da solo!)
greeting.c3
import std::io;

fn void main()
{
	io::printn("Hello, C3!");
	io::printn("I'm ready.");
}

Commenti: note per gli umani

Una cosa che vedrai spesso nel codice sono i commenti: testo ignorato dal compilatore, scritto per chi legge. In C3 ci sono tre forme:

c3
// Single-line comment: everything after the double slash is ignored.

/* Multi-line comment.
   It can go on as long as you like.
   /* And it can even contain other comments, unlike C! */
*/

<*
 Documentation comment: describes the function that follows.
 We'll use it later, because it can contain "contracts".
*>
fn void main()
{
	io::printn("Hello"); // Here too, at the end of the line.
}

Usa i commenti per spiegare il perché, non il cosa. // prints Hello non aiuta nessuno; // the greeting must be in English for the automated test sì.

Ricapitolando

  • Un programma C3 è un file di testo con estensione .c3.
  • import std::io; rende disponibili le funzioni di stampa.
  • fn void main() { ... } è il punto d’ingresso: tutto parte da qui.
  • io::printn("...") stampa e va a capo; io::print non va a capo.
  • Ogni istruzione finisce con ;.
  • c3c compile-run file.c3 compila e lancia in un colpo solo.

Nella prossima lezione vediamo come organizzare un programma in un progetto, perché un file solo ti va bene oggi ma non domani.