Dichiarare una shape
Una shape è un tipo record con campi dotati di nome e tipo:
shape Point {
x: i32,
y: i32,
}
Le parti:
shapeintroduce la dichiarazione.Pointè il nome del tipo.- Le parentesi graffe racchiudono un elenco di campi, ciascuno nella forma
name: Type.
Questa dichiarazione aggiunge allo scope corrente un nuovo tipo chiamato Point. Ovunque puoi usare un tipo integrato come i32, ora puoi usare anche Point.
Costruire un valore shape
Crea un'istanza con un'espressione letterale di struttura:
let point = Point { x: 40, y: 2 }
Il letterale indica la shape e assegna ogni campo. Ogni campo deve essere presente: Zero non imposta in silenzio a zero o a null i campi mancanti. Se ne dimentichi uno, il compilatore te lo dice:
{
"code": "FLD002",
"message": "missing field: y",
"line": 4
}
(Il codice di errore esatto può variare; il principio "nessun valore predefinito implicito" è la costante.)
Se vuoi che il tipo sia visibile, puoi annotarlo esplicitamente:
let point: Point = Point { x: 40, y: 2 }
Leggere i campi
L'accesso ai campi usa la notazione a punto:
let point = Point { x: 40, y: 2 }
let xVal = point.x
let yVal = point.y
point.x legge il campo x. Non esiste un metodo get_x(): i campi sono semplici dati.
Un esempio completo
Questo è il classico esempio point.0 del repository del linguaggio: clicca su Esegui per provarlo:
Seguilo dall'alto:
- Dichiara una shape
Pointcon due campii32. - Definisci una funzione
sumche riceve unPointe restituisce la somma dei suoi campi. - In
main, costruisci unPoint, chiamasume confronta il risultato.
Tre passaggi, tre idee legate alle shape (dichiarare, costruire, accedere) e un solo effetto: il check world.out.write(...) alla fine. Nota che sum non tocca World. È una funzione pura sui dati, e la firma lo rende evidente.
Shape con campi annidati
Una shape può contenere valori di qualsiasi tipo, comprese altre shape:
shape Range {
start: i32,
end: i32,
}
shape Segment {
label: String,
range: Range,
}
let seg = Segment {
label: "warmup",
range: Range { start: 0, end: 10 },
}
L'accesso ai campi si concatena come ti aspetti:
let len = seg.range.end - seg.range.start
Shape generiche
Quando i campi di una shape devono essere polimorfici rispetto al tipo, dichiara i parametri di tipo tra parentesi angolari:
shape Pair<T, U> {
left: T,
right: U,
}
Le istanze fissano i parametri su tipi concreti:
let intBytePair: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hello", right: "world" }
Un alias di tipo può abbreviare una parametrizzazione frequente:
type BytePair = Pair<u8, u8>
let bytes: BytePair = Pair { left: 1_u8, right: 2_u8 }
Generics approfondisce i parametri di tipo, anche sulle funzioni e non solo sulle shape.
Cosa non sono le shape
Alcune cose che potresti aspettarti da una "struct" in un altro linguaggio e che le shape di proposito non includono:
- Niente metodi. La dichiarazione di una shape contiene solo dati. Il comportamento vive in funzioni libere che ricevono la shape come parametro. Rispecchia la stessa separazione tra dati ed effetti che vedi nelle funzioni.
- Niente ereditarietà. Le shape non estendono altre shape. Se vuoi una struttura condivisa, raccoglila in un campo comune o costruisci un tipo somma con choice.
- Niente costruttori o distruttori impliciti. La costruzione è l'espressione letterale di struttura. La pulizia è esplicita: quando la libreria standard espone risorse da rilasciare, lo fa tramite API in stile capability invece che con un RAII nascosto.
- Niente campi privati. Tutti i campi di una shape sono accessibili al codice che vede il tipo della shape. La visibilità si decide a livello di tipo, non di campo.
Lo schema è questo: le shape sono tipi record semplici e prevedibili, e tutto il resto lo costruisci a partire da loro.
Quando usare una shape o una choice
Una guida rapida:
- Usa una shape quando un valore ha tutti questi campi insieme. Un
Pointha sempre sia unaxsia unay. - Usa una choice quando un valore è una tra diverse alternative. Un
Resultè o unoko unerr. - Usa un enum quando le alternative non portano dati aggiuntivi: sono solo etichette. Giorni della settimana, stati semplici.
Questi tre mattoni, cioè shape (e), choice (o) ed enum (o senza payload), coprono quasi ogni esigenza di modellazione dei dati.
Prossimo passo: generics
Hai già visto comparire Pair<T, U> di sfuggita. La prossima pagina, generics, spiega come funzionano i parametri di tipo sia sulle shape sia sulle funzioni, compresi gli schemi che ricorrono in tutta la libreria standard di Zero.
Domande frequenti
Cos'è una shape in Zero?
Una shape è il tipo prodotto di Zero, simile a una struct: un record con nome e campi tipizzati. La dichiari con shape Name { field1: T1, field2: T2 }, costruisci valori con Name { field1: v1, field2: v2 } e leggi i campi con la notazione a punto (value.field1). Le shape sono il mattone base per modellare dati strutturati.
Come si crea un valore shape?
Usa un'espressione letterale di struttura che indica la shape e assegna ogni campo: let point = Point { x: 40, y: 2 }. Ogni campo va compilato: Zero non assegna in silenzio valori predefiniti ai campi mancanti. L'ordine dei campi nel letterale non deve per forza corrispondere a quello della dichiarazione.
In cosa una shape è diversa da una classe?
Una shape è solo dati: ha campi, ma niente metodi, niente ereditarietà, niente costruttori impliciti. Le funzioni che operano su una shape la ricevono esplicitamente come parametro. Questa separazione mantiene piccolo il linguaggio e rende prevedibile il costo di costruire o copiare una shape, senza vtable o distruttori nascosti.
Le shape possono essere generiche in Zero?
Sì. Dichiara i parametri di tipo tra parentesi angolari: shape Pair<T, U> { left: T, right: U }. Le istanze fissano quei parametri: Pair<i32, u8>. Le shape generiche compaiono in tutta la libreria standard: Maybe<T>, Span<T> e così via sono tutte shape generiche o tipi somma costruiti sulla stessa idea.
Le shape passate alle funzioni vengono copiate o passate per riferimento?
Il passaggio per valore è il modello mentale predefinito per le shape: la funzione chiamata vede la propria copia logica dei dati, non un riferimento al binding del chiamante. Il modello di memoria esatto dello Zero pre-1.0 è ancora in evoluzione (negli esempi della libreria standard vedrai ref e mutref per i tipi riferimento espliciti). Per la maggior parte del codice applicativo, tratta i parametri shape come input passati per valore.