Deklarowanie shape
shape to typ rekordowy z nazwanymi polami o określonych typach:
shape Point {
x: i32,
y: i32,
}
Elementy:
shaperozpoczyna deklarację.Pointto nazwa typu.- Nawiasy klamrowe obejmują listę pól, każde w formie
name: Type.
Ta deklaracja dodaje do bieżącego zakresu nowy typ o nazwie Point. Wszędzie tam, gdzie możesz użyć typu wbudowanego, takiego jak i32, możesz teraz użyć też Point.
Tworzenie wartości shape
Instancję tworzy się wyrażeniem literału struktury:
let point = Point { x: 40, y: 2 }
Literał podaje nazwę shape i przypisuje każde pole. Każde pole musi być obecne: Zero nie ustawia po cichu brakujących pól na zero ani null. Jeśli o którymś zapomnisz, kompilator ci o tym powie:
{
"code": "FLD002",
"message": "missing field: y",
"line": 4
}
(Dokładny kod błędu może się różnić; stała jest zasada "żadnych niejawnych wartości domyślnych".)
Możesz też jawnie podać typ, jeśli chcesz, żeby był widoczny:
let point: Point = Point { x: 40, y: 2 }
Odczyt pól
Dostęp do pól odbywa się przez kropkę:
let point = Point { x: 40, y: 2 }
let xVal = point.x
let yVal = point.y
point.x odczytuje pole x. Nie ma metody get_x(): pola to zwykłe dane.
Pełny przykład
To kanoniczny przykład point.0 z repozytorium języka. Kliknij Run, aby go wypróbować:
Przejdźmy przez niego od góry:
- Zadeklaruj shape
Pointz dwoma polamii32. - Zdefiniuj funkcję
sum, która przyjmujePointi zwraca sumę jego pól. - W
mainutwórzPoint, wywołajsumi porównaj wynik.
Trzy kroki, trzy idee związane z shape (deklaracja, tworzenie, dostęp) i jeden efekt: check world.out.write(...) na końcu. Zwróć uwagę, że sum nie dotyka World. To czysta funkcja na danych, a sygnatura czyni to oczywistym.
Shape z zagnieżdżonymi polami
Shape może przechowywać wartości dowolnego typu, także inne shape:
shape Range {
start: i32,
end: i32,
}
shape Segment {
label: String,
range: Range,
}
let seg = Segment {
label: "warmup",
range: Range { start: 0, end: 10 },
}
Dostęp do pól łączy się w łańcuch, tak jak można się spodziewać:
let len = seg.range.end - seg.range.start
Generyczne shape
Gdy pola shape mają być polimorficzne względem typu, zadeklaruj parametry typu w nawiasach ostrych:
shape Pair<T, U> {
left: T,
right: U,
}
Instancje ustalają parametry jako konkretne typy:
let intBytePair: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hello", right: "world" }
Alias typu może skrócić często używaną parametryzację:
type BytePair = Pair<u8, u8>
let bytes: BytePair = Pair { left: 1_u8, right: 2_u8 }
Artykuł Generyki omawia parametry typu dokładniej, także w funkcjach, a nie tylko w shape.
Czym shape nie są
Kilka rzeczy, których można oczekiwać od "struktury" w innym języku, a których shape celowo nie mają:
- Brak metod. Deklaracja shape to same dane. Zachowanie żyje w wolnych funkcjach, które przyjmują shape jako parametr. Odzwierciedla to ten sam podział na dane i efekty, który widać w funkcjach.
- Brak dziedziczenia. Shape nie rozszerzają innych shape. Jeśli potrzebujesz wspólnej struktury, wydziel ją do wspólnego pola albo zbuduj typ sumowy przez choice.
- Brak niejawnych konstruktorów i destruktorów. Tworzenie to wyrażenie literału struktury. Sprzątanie jest jawne: gdy biblioteka standardowa udostępnia zasoby wymagające zwolnienia, odbywa się to przez API w stylu capabilities, a nie ukryte RAII.
- Brak pól prywatnych. Wszystkie pola shape są dostępne dla kodu, który widzi typ shape. Widoczność działa na poziomie typu, a nie pola.
Wzorzec jest taki: shape to proste, przewidywalne typy rekordowe, a wszystko inne buduje się z nich.
Kiedy shape, a kiedy choice
Krótki przewodnik:
- Użyj shape, gdy wartość ma wszystkie te pola naraz.
Pointzawsze ma zarównox, jak iy. - Użyj choice, gdy wartość jest jedną z kilku możliwości.
Resultto albook, alboerr. - Użyj enum, gdy możliwości nie niosą dodatkowych danych, tylko są etykietami. Dni tygodnia, proste stany.
Te trzy elementy, czyli shape (i), choice (lub) oraz enum (lub bez danych), pokrywają niemal każdą potrzebę modelowania danych.
Dalej: generyki
Pair<T, U> pojawiało się już mimochodem. Następny artykuł, generyki, wyjaśnia, jak działają parametry typu zarówno w shape, jak i w funkcjach, łącznie ze wzorcami występującymi w całej bibliotece standardowej Zero.
Najczęściej zadawane pytania
Czym jest shape w Zero?
shape to typ produktowy Zero podobny do struktury: nazwany rekord z polami o określonych typach. Deklarujesz go przez shape Name { field1: T1, field2: T2 }, tworzysz wartości przez Name { field1: v1, field2: v2 }, a pola czytasz składnią z kropką (value.field1). Shape to podstawowy element modelowania danych strukturalnych.
Jak utworzyć wartość shape?
Użyj wyrażenia literału struktury, które podaje nazwę shape i przypisuje każde pole: let point = Point { x: 40, y: 2 }. Każde pole musi być wypełnione: Zero nie uzupełnia po cichu brakujących pól wartościami domyślnymi. Kolejność pól w literale nie musi odpowiadać deklaracji.
Czym shape różni się od klasy?
Shape to zwykłe dane: ma pola, ale nie ma metod, dziedziczenia ani niejawnych konstruktorów. Funkcje działające na shape przyjmują go jawnie jako parametr. Ten podział utrzymuje język małym, a koszt budowania lub kopiowania shape przewidywalnym, bez ukrytych vtable czy destruktorów.
Czy shape w Zero mogą być generyczne?
Tak. Zadeklaruj parametry typu w nawiasach ostrych: shape Pair<T, U> { left: T, right: U }. Instancje ustalają te parametry: Pair<i32, u8>. Generyczne shape występują w całej bibliotece standardowej: Maybe<T>, Span<T> i podobne to generyczne shape lub typy sumowe oparte na tej samej idei.
Czy shape są kopiowane czy przekazywane przez referencję?
Domyślny model myślowy dla shape to przekazywanie przez wartość: wywoływana funkcja widzi własną logiczną kopię danych, a nie referencję do wiązania wywołującego. Dokładny model pamięci w Zero przed wersją 1.0 wciąż się kształtuje (w przykładach biblioteki standardowej zobaczysz ref i mutref jako jawne typy referencyjne). W większości kodu aplikacji traktuj parametry shape jako wejścia przekazywane przez wartość.