object: собственные типы данных
Разбираемся, как в Nim описать собственную структуру данных с именованными полями — и почему это одновременно похоже и непохоже на классы Python.
object — тип данных в Nim, который объединяет несколько именованных полей разных типов в одну сущность, например «ученик» с именем, возрастом и оценкой.
Зачем нужен собственный тип
Представьте, что вы храните данные об ученике: имя, возраст, средний балл. Можно завести три отдельные переменные — imya, vozrast, ball — но это неудобно и опасно: ничего не мешает случайно перепутать порядок при передаче в функцию, а если учеников много, придётся плодить переменные с номерами вроде imya1, imya2. В Python для такой задачи обычно заводят класс или используют словарь. В Nim есть свой инструмент — object, который позволяет объявить новый тип данных с именованными полями и работать с ним как с единым целым.
object в Nim — это не класс в привычном смысле ООП. У него нет встроенных методов внутри тела типа, нет наследования по умолчанию и нет конструктора, который вы обязаны писать. Это просто «чертёж» структуры данных: список полей и их типов. Функции, которые работают с этим типом, объявляются отдельно, вне описания самого object — подробнее об этом будет отдельный раздел учебника.
Объявление типа
Тип объявляется в блоке type, с указанием имени (по соглашению — с заглавной буквы) и списком полей с их типами.
type
Uchenik = object
imya: string
vozrast: int
ball: float
var petya = Uchenik(imya: "Petya", vozrast: 14, ball: 4.5)
echo petya.imya, " — ", petya.vozrast, " лет, балл ", petya.ballВывод:
Petya — 14 лет, балл 4.5Разбираем построчно
Строка type открывает блок объявления типов — в Nim типы, в отличие от обычных переменных, описываются в отдельном блоке, похожем на блок var для переменных. Дальше Uchenik = object говорит: «создаю новый тип с именем Uchenik, и это структура-объект». Три следующие строки с отступом — поля этого типа: imya типа string, vozrast типа int, ball типа float. Обратите внимание на синтаксис поля — это то же самое имя: тип, что вы уже видели при явной типизации обычных переменных.
Дальше идёт создание экземпляра: Uchenik(imya: "Petya", vozrast: 14, ball: 4.5) — это вызов типа как конструктора, куда поля передаются по имени, через двоеточие, в любом порядке (можно было бы написать vozrast раньше imya, и код остался бы рабочим). Результат сохраняется в переменную petya через var, потому что мы, возможно, захотим её позже изменить. Наконец, обращение к полю происходит через точку: petya.imya, petya.vozrast — синтаксис, знакомый каждому, кто хоть немного работал с объектами в Python или JavaScript.
Сравнение с классами Python
В Python похожая структура выглядела бы так:
class Uchenik:
def __init__(self, imya, vozrast, ball):
self.imya = imya
self.vozrast = vozrast
self.ball = ball
petya = Uchenik("Petya", 14, 4.5)Разница сразу бросается в глаза: в Python нужно вручную написать метод __init__, который принимает аргументы и присваивает их полям self. В Nim это делать не нужно — конструктор с именованными полями создаётся автоматически, просто из описания типа. Это экономит время на простых структурах данных, но есть и обратная сторона: если полей много, вызов вида Uchenik(imya: ..., vozrast: ..., ball: ...) с явными именами полей — это стандартный и рекомендуемый стиль, в отличие от Python, где чаще пишут позиционные аргументы без имён.
Ещё одно отличие — производительность и хранение в памяти. Объекты Python — это словари под капотом (в упрощённом виде), гибкие, но с накладными расходами. object в Nim по умолчанию компилируется в компактный блок памяти фиксированного размера, как struct в языке C — отсюда и разговор о «скорости C» в названии этого учебника. Поля объекта лежат в памяти подряд, без лишней обёртки, и обращение к ним практически бесплатно по времени выполнения.
Изменение полей
Поля объекта, объявленного через var, можно менять после создания — так же, как обычную переменную:
type
Uchenik = object
imya: string
ball: float
var masha = Uchenik(imya: "Masha", ball: 3.5)
masha.ball = 5.0
echo masha.imya, " исправила балл на ", masha.ballВывод:
Masha исправила балл на 5.0Если бы masha была объявлена через let (неизменяемая переменная), строка masha.ball = 5.0 вызвала бы ошибку компиляции — компилятор Nim запрещает менять поля объекта, привязанного к let, точно так же, как запрещает переприсваивать саму переменную.
Мини-пример: карточка книги
Соберём тип для практической задачи — карточка книги в каталоге библиотеки.
type
Kniga = object
nazvanie: string
avtor: string
stranits: int
let knigi = [
Kniga(nazvanie: "Dune", avtor: "Herbert", stranits: 412),
Kniga(nazvanie: "1984", avtor: "Orwell", stranits: 328)
]
for k in knigi:
echo k.nazvanie, " (", k.avtor, "), ", k.stranits, " стр."Вывод:
Dune (Herbert), 412 стр.
1984 (Orwell), 328 стр.Здесь мы создали массив из двух объектов типа Kniga и прошлись по нему циклом for, обращаясь к полям каждого элемента через точку. Именно так на практике чаще всего и используют object — не поодиночке, а коллекциями, что естественным образом подводит нас к следующему уроку про seq.
Частые ошибки новичков
Первая ошибка — забыть, что object объявляется внутри блока type, а не как обычная переменная. Написать var Uchenik = object ... вместо type Uchenik = object ... — частая опечатка у тех, кто только начал.
Вторая ошибка — попытаться создать экземпляр без имён полей, по одной лишь позиции, как в Python (Uchenik("Petya", 14, 4.5)). Такой синтаксис в Nim тоже допустим при определённых условиях, но начинающих он часто путает с полем не на своём месте — поэтому именованный синтаксис с двоеточиями надёжнее и на первых порах его стоит использовать всегда.
Третья ошибка — пытаться изменить поле объекта, объявленного через let. Компилятор Nim строго следит за неизменяемостью, и ошибка «attempt to modify readonly variable» — частый сюрприз для тех, кто привык к более расслабленному Python.
Итоги
objectпозволяет объявить собственный тип данных с именованными полями — аналог класса-структуры без методов внутри тела типа.- Экземпляр создаётся вызовом типа как конструктора с полями по имени:
Тип(поле: значение, ...). - В отличие от объектов Python,
objectNim компилируется в компактный блок памяти без накладных расходов — источник «скорости C». - Поля можно менять только у объекта, привязанного к
var— уlet-объекта поля защищены от изменения так же, как и сама переменная.