Un solo modo per dare un nome a un valore
In Zero dai un nome a un valore con let:
let answer = 42
Questa è tutta la sintassi. Non c'è var, non c'è const, non c'è auto. Una sola parola chiave di binding mantiene piccolo il linguaggio: agenti e persone la imparano una volta e la applicano ovunque.
let introduce un binding locale nello scope corrente. Dopo questa riga, answer fa riferimento a 42 fino alla fine del blocco che lo contiene.
Inferenza dei tipi
Il compilatore deduce il tipo dal lato destro. Il letterale 42 ha tipo i32 per impostazione predefinita, quindi answer è un i32. Il letterale "ciao" è una stringa, quindi:
let greeting = "ciao"
associa greeting a un valore stringa. Se chiami una funzione che restituisce un Pair<i32, u8>, il binding ha tipo Pair<i32, u8>:
let pair = makePair(40, 2_u8)
Non devi scrivere il tipo su ogni binding, e questo mantiene il codice leggibile.
Annotazioni di tipo esplicite
Quando vuoi documentare il tipo, o imporne uno specifico quando l'inferenza ne sceglierebbe un altro, scrivi il tipo dopo i due punti:
let count: u8 = 10
let pair: Pair<i32, u8> = makePair(40, 2_u8)
Le annotazioni sono anche un'indicazione per il compilatore quando un letterale potrebbe essere di diversi tipi. Il letterale 10 potrebbe essere i32, i64, u8 e così via; l'annotazione lo fissa.
Come alternativa all'annotazione del binding vedrai anche suffissi di tipo sui letterali:
let small = 10_u8 // u8 grazie al suffisso del letterale
let big = 10_i64 // i64 grazie al suffisso del letterale
Entrambe le forme sono valide; scegli quella che rende più chiara l'intenzione nel punto in cui la usi.
Binding in azione
Un esempio completo che usa sia la forma dedotta sia quella esplicita: clicca su Esegui per provarlo:
point è annotato esplicitamente perché il lato destro è un letterale di struttura. total è dedotto: sum è dichiarata con ritorno i32, quindi anche il binding è i32.
Scope e shadowing
Un binding let è valido dalla riga in cui viene dichiarato fino alla fine del blocco che lo contiene. I blocchi annidati creano nuovi scope:
pub fun main(world: World) -> Void raises {
let value = 1
if true {
let value = 2 // nasconde il 'value' esterno dentro questo blocco
// qui, value == 2
}
// di nuovo fuori dall'if, value == 1
}
Il value interno non modifica quello esterno: è un binding separato che esce dallo scope alla parentesi graffa di chiusura del blocco if. È lo stesso modello di Rust e dei linguaggi della famiglia ML. È particolarmente comune quando vuoi trasformare un valore attraverso una serie di passaggi senza inventare nuovi nomi per ogni risultato intermedio.
Cosa non fa let
Alcune cose che potresti aspettarti da altri linguaggi e che let di proposito non include:
- Dichiarazioni con il solo tipo. Non esiste una forma
let x: i32;che introduce un binding non inizializzato. Un binding deve avere un valore nel punto in cui viene dichiarato. - Destrutturazione (per ora). Alcuni linguaggi ti permettono di scrivere
let (a, b) = pair. Zero è piccolo per scelta e al momento si concentra sui semplici binding di nomi: controlla la documentazione attuale per sapere se la destrutturazione è arrivata. - Più parole chiave per durate diverse. Niente
static,const,let mutseparati, né varianti con scope di blocco o di funzione. Una sola parola chiave.
Se vieni da JavaScript, l'analogia più vicina è const: un nome associato a un valore per il resto del blocco, con shadowing negli scope interni. Se vieni da Rust, qui let svolge lo stesso ruolo del let di Rust, senza la parola chiave esplicita mut.
Uno schema: costruire un valore passo dopo passo
I binding danno il meglio quando vuoi scrivere un calcolo come una serie di passaggi intermedi con nome. Va bene sia per le persone che leggono sia per gli agenti che ragionano localmente su ogni riga:
Ogni riga introduce un nuovo dato su cui il resto della funzione può lavorare. Il compilatore produce comunque codice compatto: dare un nome ai valori intermedi non ha alcun costo a runtime.
Prossimo passo: tipi primitivi
let non significa molto senza qualcosa da associare. La prossima pagina presenta i tipi primitivi di Zero: le varie ampiezze degli interi, i float, le stringhe e i tipi Void e Bool che vedrai più spesso.
Domande frequenti
Come si dichiara una variabile in Zero?
Usa let. La forma è let name = value per un tipo dedotto, oppure let name: Type = value per scrivere il tipo esplicitamente. Per esempio: let answer = 42 o let answer: i32 = 42. Entrambe associano il nome answer al valore 42 nello scope corrente.
Zero deduce i tipi dei binding let?
Sì. Se scrivi let total = sum(point) e sum restituisce i32, il tipo del binding viene dedotto come i32. Puoi comunque annotarlo esplicitamente quando vuoi documentare il tipo o imporne uno specifico, per esempio let count: u8 = 10.
I binding let in Zero sono mutabili?
Un semplice let introduce un binding locale da usare nel suo scope. In Zero pre-1.0 la gestione della mutabilità è ancora in evoluzione: il linguaggio punta su effetti espliciti e memoria prevedibile, quindi tutto ciò che modifica lo stato tramite un binding deve renderlo visibile. Controlla la documentazione attuale di Zero per la sintassi esatta della mutabilità nella tua versione della toolchain.
Qual è la differenza tra let e const in Zero?
Zero usa let per i normali binding locali nel corpo delle funzioni. Non espone più parole chiave di binding come let/const/var di JavaScript: mantenere piccola la superficie è una scelta di design deliberata. Le costanti di compilazione si esprimono di solito tramite il sistema dei tipi o le dichiarazioni di primo livello, non con una parola chiave separata.
Si può ridichiarare un binding let in Zero?
I binding vivono nel loro scope contenitore. Un nuovo let con lo stesso nome in uno scope annidato è un binding separato che nasconde quello esterno per la durata dello scope interno: il binding esterno non viene toccato e torna valido quando lo scope interno finisce. È lo stesso modello usato da Rust e dai linguaggi della famiglia ML.