LEARN X · ЗА 15 МИН

Nim

Nim за 15 минут: весь язык на одной странице через закомментированный код — переменные, циклы, seq, proc, object, дженерики, ref/ptr, итераторы.

Nim — компилируемый язык с Python-подобным синтаксисом и скоростью C. Статическая типизация с выводом типов, отступы вместо скобок, метапрограммирование. Ниже весь язык на одной странице: читаем код сверху вниз, всё объяснено в комментариях.

1. Вывод и комментарии

# Однострочный комментарий начинается с #

echo "Привет, Nim!"  # echo печатает строку и перевод строки

#[
  Это многострочный (блочный) комментарий.
  Он может занимать несколько строк
  и даже #[ вкладываться ]# внутрь себя.
]#

echo 2, " ", 3       # echo принимает несколько аргументов: 2 3
echo "a", "b", "c"   # склеит без пробелов: abc

## Документационный комментарий (две решётки) — попадает в docs.

2. Переменные

Три способа объявления: var (изменяемая), let (неизменяемая после присваивания), const (вычисляется при компиляции).

var x = 10           # var — изменяемая переменная, тип int выведен автоматически
x = 20               # можно переприсвоить

let y = 3.14         # let — связывается один раз, дальше менять нельзя (тип float)
# y = 1.0            # ОШИБКА компиляции: 'y' нельзя изменить

const PI = 3.14159   # const — известна на этапе компиляции (только из const-выражений)

# Явное указание типа: имя: Тип = значение
var age: int = 30
var price: float = 9.99
var ok: bool = true          # bool: true / false
var name: string = "Анна"    # string — строка (UTF-8)
var letter: char = 'A'       # char — один байт, в одинарных кавычках

# Объявление без значения — получит "нулевое" значение типа
var counter: int             # counter == 0
var flag: bool               # flag == false

# Несколько переменных сразу
var
  a = 1
  b = 2
  c = 3

echo a + b + c       # 6

3. Строки

import std/strutils   # модуль со строковыми утилитами

let first = "Иван"
let last = "Петров"

# Конкатенация оператором &
let full = first & " " & last
echo full            # Иван Петров

# Интерполяция строк через strformat
import std/strformat
let n = 5
echo fmt"Число: {n}, квадрат: {n*n}"   # Число: 5, квадрат: 25

# Многострочный строковый литерал в тройных кавычках
let text = """
Первая строка
Вторая строка
"""

# Полезные методы (вызываются как процедуры или через точку — см. секцию 8)
echo "  hello  ".strip()        # "hello" — убирает пробелы по краям
echo toUpperAscii("abc")        # ABC
echo "abc".toUpperAscii()       # ABC — то же через точку (UFCS, см. секцию 8)
echo "a,b,c".split(",")         # @["a", "b", "c"]
echo "hello".len                # 5 — длина строки
echo "hello"[0]                 # 'h' — индексация по символам
echo "hello"[1..3]              # "ell" — срез (slice)
echo "hi".repeat(3)             # hihihi
echo "Nim".contains("i")        # true

4. Операторы и условия

# Арифметика
echo 7 + 3      # 10
echo 7 - 3      # 4
echo 7 * 3      # 21
echo 7 div 3    # 2   — целочисленное деление (ключевое слово div)
echo 7 mod 3    # 1   — остаток (mod)
echo 7 / 3      # 2.3333... — / всегда даёт float
echo 2 ^ 10     # 1024 — возведение в степень (нужен import std/math)

# Сравнение возвращает bool: ==  !=  <  >  <=  >=
echo 5 > 3            # true
echo 5 == 5          # true
echo 5 != 4          # true

# Логические: and / or / not
echo (true and false)   # false
echo (true or false)    # true
echo not true           # false

# if / elif / else — ветвление по отступам
let score = 75
if score >= 90:
  echo "Отлично"
elif score >= 60:
  echo "Норма"          # сработает эта ветка
else:
  echo "Плохо"

# if — это выражение, можно присвоить результат
let sign = if score >= 0: "плюс" else: "минус"
echo sign               # плюс

# case — выбор по значению (аналог switch)
let day = 3
case day
of 1, 2, 3, 4, 5:
  echo "Будни"          # сработает
of 6, 7:
  echo "Выходные"
else:
  echo "Нет такого дня"

# when — "if на этапе компиляции" (по условиям, известным компилятору)
when defined(windows):
  echo "Сборка под Windows"
else:
  echo "Не Windows"

5. Циклы

# for с диапазоном: a..b включает оба конца
for i in 0..3:
  echo i               # 0 1 2 3 (по строкам)

# a..<b — верхняя граница НЕ включается
for i in 0..<3:
  echo i               # 0 1 2

# Шаг через countup / countdown
for i in countup(0, 10, 2):
  echo i               # 0 2 4 6 8 10

# for-in по коллекции
for fruit in ["яблоко", "груша"]:
  echo fruit

# pairs — индекс и значение сразу
for idx, val in ["a", "b", "c"]:
  echo idx, " -> ", val   # 0 -> a ; 1 -> b ; 2 -> c

# while — пока условие истинно
var k = 0
while k < 3:
  echo "k=", k
  inc k                # inc увеличивает на 1 (есть и dec)

# break — выйти из цикла, continue — к следующей итерации
for i in 0..10:
  if i == 2: continue  # пропустить 2
  if i == 5: break     # остановиться на 5
  echo i               # 0 1 3 4

6. Коллекции

array — фиксированный размер, seq — динамический, Table — словарь, set / HashSet — множества.

# Массив (array) — размер фиксирован и известен при компиляции
var arr: array[3, int] = [10, 20, 30]
echo arr[0]            # 10
arr[1] = 99
echo arr               # [10, 99, 30]
echo arr.len           # 3

# Последовательность (seq) — динамический массив
var nums = @[1, 2, 3]  # @[...] создаёт seq
nums.add(4)            # добавить элемент
echo nums              # @[1, 2, 3, 4]
echo nums.len          # 4
nums.delete(0)         # удалить по индексу
echo nums              # @[2, 3, 4]

# Перебор и преобразования (нужен import std/sequtils)
import std/sequtils
let doubled = @[1, 2, 3].map(proc(x: int): int = x * 2)
echo doubled           # @[2, 4, 6]
echo @[1, 2, 3, 4].filter(proc(x: int): bool = x mod 2 == 0)  # @[2, 4]

# Таблица (Table) — пары ключ-значение
import std/tables
var ages = {"Анна": 30, "Борис": 25}.toTable
echo ages["Анна"]      # 30
ages["Вера"] = 40      # добавить/изменить
echo ages.hasKey("Борис")   # true
for name, age in ages:
  echo name, ": ", age

# Множество (set) — для перечислимых/малых типов
var letters: set[char] = {'a', 'b', 'c'}
echo 'b' in letters    # true
letters.incl('d')      # добавить
letters.excl('a')      # удалить

# HashSet — множество произвольных значений
import std/sets
var s = toHashSet([1, 2, 2, 3])
echo s.len             # 3 — дубликаты схлопнулись

7. Процедуры и функции

proc — обычная процедура, func — гарантированно без побочных эффектов (чистая функция).

# proc имя(параметры): ТипРезультата = тело
proc add(a: int, b: int): int =
  return a + b

echo add(2, 3)         # 5

# Последнее выражение и так становится результатом — return не обязателен
proc square(x: int): int =
  x * x

echo square(4)         # 16

# Параметры по умолчанию
proc greet(name: string, greeting: string = "Привет"): string =
  greeting & ", " & name

echo greet("Аня")              # Привет, Аня
echo greet("Аня", "Здарова")   # Здарова, Аня

# Неявная переменная result — это "возвращаемое значение", доступна сразу
proc sumTo(n: int): int =
  for i in 1..n:
    result += i        # result уже инициализирован нулём
  # тело само вернёт result

echo sumTo(5)          # 15

# func — чистая функция: компилятор запрещает побочные эффекты (echo, var-глобалки)
func cube(x: int): int =
  x * x * x

echo cube(3)           # 27

# var-параметр передаётся по ссылке — функция может его менять
proc double(x: var int) =
  x = x * 2

var value = 21
double(value)
echo value             # 42

8. Синтаксис вызовов (UFCS)

Uniform Function Call Syntax: первый аргумент можно вынести "перед точкой". len(x) и x.len — одно и то же.

# Эти две строки полностью эквивалентны:
echo len("hello")      # 5
echo "hello".len       # 5

# Любую proc можно звать в "методном" стиле
proc square(x: int): int = x * x
echo square(5)         # 25
echo 5.square          # 25 — то же самое

# Цепочки вызовов читаются слева направо
import std/strutils
echo "  Hello World  ".strip().toLowerAscii().split(" ")
# @["hello", "world"]

# Скобки можно опускать, если нет неоднозначности
echo "hello".len       # без скобок у len

# Поэтому в Nim нет разницы между "методами" и "функциями" —
# x.foo(y) == foo(x, y). Это и есть UFCS.

9. Типы: object, наследование, enum, tuple

# object — структура с именованными полями
type
  Point = object
    x: int
    y: int

var p = Point(x: 3, y: 4)
echo p.x, ", ", p.y    # 3, 4
p.x = 10               # поля изменяемы, если переменная var

# Наследование: object of Родитель + ref для полиморфизма
type
  Animal = ref object of RootObj
    name: string
  Dog = ref object of Animal
    breed: string

let d = Dog(name: "Рекс", breed: "лайка")
echo d.name            # Рекс — поле унаследовано от Animal

# enum — перечисление
type
  Color = enum
    Red, Green, Blue

var c = Green
echo c                 # Green
echo ord(Blue)         # 2 — порядковый номер

# tuple — кортеж: набор полей, можно безымянных
var person = (name: "Аня", age: 30)
echo person.name       # Аня
echo person.age        # 30

let pair = (1, "один") # безымянный кортеж
echo pair[0], " ", pair[1]   # 1 один

# Распаковка кортежа
let (nm, ag) = ("Боря", 25)
echo nm, " ", ag       # Боря 25

10. Дженерики и шаблоны

Дженерики параметризуют код типом [T]. Шаблоны (template) — подстановка кода на этапе компиляции.

# Дженерик: [T] — параметр-тип, подставляется при вызове
proc maxOf[T](a: T, b: T): T =
  if a > b: a else: b

echo maxOf[int](3, 7)        # 7
echo maxOf(2.5, 1.5)         # 2.5 — T выведен автоматически
echo maxOf("abc", "xyz")     # xyz — работает и со строками

# Дженерик-тип: контейнер любого типа
type
  Box[T] = object
    value: T

var b = Box[string](value: "привет")
echo b.value                 # привет

# template — мини-макрос: тело подставляется в место вызова
template twice(body: untyped) =
  body
  body

twice:
  echo "эхо"                 # напечатает "эхо" дважды

# template как короткий синоним выражения
template isEven(n: int): bool = n mod 2 == 0
echo isEven(4)               # true

11. Управление памятью и указатели

Nim управляет памятью автоматически (ARC/ORC). ref — безопасная управляемая ссылка, ptr — сырой указатель (для FFI и низкого уровня).

# Обычные значения копируются и живут на стеке — память не волнует.
var a = 5
var b = a              # b — независимая копия
b = 99
echo a                 # 5 (не изменилось)

# ref — управляемая ссылка (на куче), её освободит сборщик памяти
type
  Node = ref object
    value: int
    next: Node          # ссылка на такой же узел (рекурсивный тип)

var head = Node(value: 1)
head.next = Node(value: 2)
echo head.value          # 1
echo head.next.value     # 2

# new создаёт ref-объект вручную
var r: ref int
new(r)                   # выделить память
r[] = 42                 # [] — разыменование (доступ к значению)
echo r[]                 # 42

# ptr — сырой небезопасный указатель (обычно только для C-интеропа)
var x = 10
let px = addr x          # addr берёт адрес переменной
echo px[]                # 10 — разыменование
px[] = 77                # запись по адресу
echo x                   # 77

12. Полезное: итераторы и перегрузка операторов

# iterator — ленивая последовательность для for. Возвращает значения через yield
iterator countTo(n: int): int =
  var i = 1
  while i <= n:
    yield i              # отдать очередное значение
    inc i

for v in countTo(3):
  echo v                 # 1 2 3

# Итератор по чётным числам
iterator evens(limit: int): int =
  var i = 0
  while i <= limit:
    yield i
    i += 2

for e in evens(8):
  echo e                 # 0 2 4 6 8

# Перегрузка операторов: оператор — это обычная proc с символьным именем
type
  Vec = object
    x, y: int

proc `+`(a, b: Vec): Vec =     # имя оператора в обратных кавычках
  Vec(x: a.x + b.x, y: a.y + b.y)

let v = Vec(x: 1, y: 2) + Vec(x: 3, y: 4)
echo v.x, ", ", v.y      # 4, 6

# Перегрузка $ задаёт строковое представление (как __str__ в Python)
proc `$`(v: Vec): string =
  "Vec(" & $v.x & ", " & $v.y & ")"

echo Vec(x: 5, y: 9)     # Vec(5, 9)

# На этом экспресс-тур по Nim завершён. Дальше: nimble (пакеты),
# async/await, макросы (macro) и компиляция в C/JS.
Поддержать проект