Объявление shape
shape — это тип-запись с именованными типизированными полями:
shape Point {
x: i32,
y: i32,
}
Части:
shapeвводит объявление.Point— имя типа.- Скобки оборачивают список полей, каждое —
name: Type.
Это объявление добавляет в текущую область видимости новый тип под именем Point. Везде, где можно использовать встроенный тип вроде i32, теперь можно использовать и Point.
Конструирование значения shape
Создайте экземпляр через struct-литерал:
let point = Point { x: 40, y: 2 }
Литерал называет shape и присваивает каждое поле. Все поля должны присутствовать — Zero не подставляет тихо нули или null для пропущенных полей. Если одно забыли, компилятор скажет:
{
"code": "FLD002",
"message": "missing field: y",
"line": 4
}
(Точный код ошибки может отличаться; принцип «без неявных умолчаний» постоянен.)
Тип можно прочитать или аннотировать явно, если хочется, чтобы он был видим:
let point: Point = Point { x: 40, y: 2 }
Чтение полей
Доступ к полям — через точку:
let point = Point { x: 40, y: 2 }
let xVal = point.x
let yVal = point.y
point.x читает поле x. Никаких методов get_x() — поля это просто данные.
Полный разобранный пример
Это каноничный пример point.0 из репозитория языка — нажмите Run, чтобы попробовать:
Пройдём сверху вниз:
- Объявляем shape
Pointс двумя полямиi32. - Определяем функцию
sum, которая принимаетPointи возвращает сумму его полей. - В
mainконструируемPoint, вызываемsumи сравниваем результат.
Три шага, три идеи, связанные с shape (объявить, сконструировать, обратиться), и один эффект — check world.out.write(...) в конце. Заметьте, что sum не трогает World. Это чистая функция над данными, и сигнатура это очевидно показывает.
Shapes с вложенными полями
Shape может хранить значения любого типа, включая другие shapes:
shape Range {
start: i32,
end: i32,
}
shape Segment {
label: String,
range: Range,
}
let seg = Segment {
label: "warmup",
range: Range { start: 0, end: 10 },
}
Доступ к полям цепляется как и ожидается:
let len = seg.range.end - seg.range.start
Дженерик-shapes
Когда поля shape должны быть типо-полиморфными, объявите параметры типа в угловых скобках:
shape Pair<T, U> {
left: T,
right: U,
}
Экземпляры фиксируют параметры конкретными типами:
let intBytePair: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hello", right: "world" }
Type alias может сократить частую параметризацию:
type BytePair = Pair<u8, u8>
let bytes: BytePair = Pair { left: 1_u8, right: 2_u8 }
Generics разбирает параметры типа глубже — в том числе на функциях, не только на shapes.
Чем shapes не являются
Несколько вещей, которые могут ожидаться от «структуры» в другом языке, но которых shapes намеренно не имеют:
- Никаких методов. Объявление shape — это только данные. Поведение живёт в свободных функциях, которые принимают shape параметром. Это зеркалит то же разделение между данными и эффектами, что и в функциях.
- Никакого наследования. Shapes не наследуются друг от друга. Если нужна общая структура, либо вынесите её в общее поле, либо стройте sum-тип через choice.
- Никаких неявных конструкторов или деструкторов. Конструирование — это struct-литерал. Очистка — явная. Когда стандартная библиотека выставляет ресурсы, которые надо освобождать, это делается через capability-стиль API, а не через скрытый RAII.
- Никаких приватных полей. Все поля shape доступны коду, который может видеть тип shape. Видимость — на уровне типа, а не поля.
Паттерн такой: shapes — простые предсказуемые типы-записи, из которых вы строите всё остальное.
Когда shape, а когда choice
Короткая шпаргалка:
- Используйте shape, когда у значения все эти поля присутствуют вместе. У
Pointвсегда есть иx, иy. - Используйте choice, когда значение — одно из нескольких альтернатив.
Result— это илиok, илиerr. - Используйте enum, когда альтернативы не несут дополнительных данных — это просто метки. Дни недели, простые состояния.
Эти три строительных блока — shape (и), choice (или), enum (или без payload) — покрывают почти любую потребность в моделировании данных.
Дальше: Generics
Вы уже видели мельком Pair<T, U>. Следующая статья, generics, объясняет, как параметры типа работают и на shapes, и на функциях, включая паттерны, которые появляются по всей стандартной библиотеке Zero.
Часто задаваемые вопросы
Что такое shape в Zero?
shape — это struct-подобный product-тип в Zero, именованная запись с типизированными полями. Объявляете через shape Name { field1: T1, field2: T2 }, конструируете значения через Name { field1: v1, field2: v2 }, читаете поля через точку (value.field1). Shapes — строительный блок для моделирования структурированных данных.
Как создать значение shape?
Используйте struct-литерал, который называет shape и присваивает каждое поле: let point = Point { x: 40, y: 2 }. Каждое поле должно быть заполнено — Zero не подставляет тихо умолчания для пропущенных полей. Порядок полей в литерале не обязан совпадать с порядком в объявлении.
Чем shape отличается от класса?
Shape — это просто данные: есть поля, нет методов, нет наследования, нет неявных конструкторов. Функции, оперирующие shape, принимают его параметром явно. Это разделение держит язык маленьким и делает стоимость построения или копирования shape предсказуемой, без скрытых vtable или деструкторов.
Могут ли shapes быть дженериками в Zero?
Да. Объявите параметры типа в угловых скобках: shape Pair<T, U> { left: T, right: U }. Экземпляры закрепляют эти параметры: Pair<i32, u8>. Дженерик-shapes встречаются по всей стандартной библиотеке — Maybe<T>, Span<T> и так далее — всё это дженерик-shapes или sum-типы, построенные на той же идее.
Shapes копируются или передаются по ссылке в функции?
Передача по значению — дефолтная ментальная модель для shapes: вызываемая функция видит свою логическую копию данных, а не ссылку на привязку вызывающего. Точная модель памяти в pre-1.0 Zero ещё эволюционирует (в примерах стандартной библиотеки встретятся ref и mutref для явных типов ссылок). Для большинства прикладного кода считайте shape-параметры значениями.