- PVSM.RU - https://www.pvsm.ru -

Мой опыт разработки на языке Nim

Мой опыт разработки на языке Nim - 1

Привет!

Уже довольно давно я пишу свой игровой фреймворк — такой pet project для души. А так как для души нужно выбирать что-то, что нравится (а в данном случае — на чём нравится писать), то выбор мой пал на nim. В этой статье я хочу поговорить именно про nim, про его особенности, плюсы и минусы, а тема геймдева лишь задаёт контекст моего опыта — какие задачи я решал, какие трудности возникли.

Давным-давно, когда трава была зеленее, а небо чище, я встретил nim. Хотя нет, не так. Давным-давно я хотел заниматься разработкой игр, чтобы написать свою Самую Классную Игру — думаю, многие проходили через это. В те времена Unity и Unreal Engine только-только стали появляться на слуху и, вроде как, ещё не были бесплатными. Я не стал их использовать, не столько из-за жадности, сколько из-за желания написать всё самому, создать игровой мир полность с нуля, с самого первого нулевого байта. Да, долго, да, сложно, зато сам процесс приносит удовольствие — а что ещё для счастья надо?

Вооружившись Страуструпом и Qt, я хлебнул говна по самое небалуй, потому что, во-первых, не был одним из 10 человек в мире, знающих C++ хорошо, а, во-вторых, плюсы активно вставляли мне палки в колёса. Не вижу смысла повторять то, что за меня уже замечательно написал platoff [1]:

Как я нашел лучший в мире язык программирования. Часть 1 [2]
Как я нашел лучший в мире язык программирования. Часть 2 [3]
Как я нашел лучший в мире язык программирования. Часть Йо (2.72) [4]

Это безумный кайф, когда ты пишешь код свободно, почти не думая, не ожидая core dumped перед каждым запуском, когда фичи добавляются прямо на глазах, вот теперь мы так можем, а теперь еще так, то скажите мне пожалуйста, какая мне разница что у меня нет темплейтов, если я даже не скучал по ним? Продуктивность — вот главная цель программиста, который делает вещи, и единственная задача инструмента который он использует.

Работая с C++, я постоянно думал, как мне написать то, что я хочу, а не что мне написать. Поэтому я перешёл на nim. С историей покончено, давайте же я поделюсь с вами опытом после нескольких лет работы на nim.

Общие сведения для тех, кто не в курсе

  • Компилятор открытый (MIT), разрабатывается энтузиастами. Создатель языка — Andreas Rumpf (Araq). Второй разработчик — Dominik Picheta (dom96), написавший книгу Nim in action [5]. Также некоторое время назад компания Status [6] стала спонсировать разработку языка, благодаря чему у nim'а появились ещё 2 фуллтайм-разработчика. Помимо них, разумеется, контрибутят и другие люди.
  • Недавно вышла версия 1.0 [7], а это значит, что язык стабилен и "breaking changes" больше не ожидаются. Если раньше вы не хотели использовать unstable версию, потому что обновления могли сломать приложение, то теперь самое время попробовать nim в своих проектах.
  • Nim компилируется (или транспилируется) в C, C++ (которые далее компилируются в нативный код) или JS (с некоторыми ограничениями). Соотвественно, при помощи FFI вам доступны все существующие библиотеки для C и C++. Если нет нужного пакета на nim — поищите на си или плюсах.
  • Ближайшие языки — python (по синтаксису, на первый взгляд) и D (по функционалу) — имхо

Документация

Вообще-то с этим плохо. Проблемы:

  1. Документация раскидана по разным источникам
  2. Документация гавно не в полной мере описывает все возможности языка
  3. Документация порой слишком лаконична

Пример: хотите вы написать многопоточное приложения, ядер-то много, а девать некуда.
Вот раздел официальной документации про потоки [8]. Нет, понимаете, потоки — это отдельная большая часть языка, его фича, которую даже нужно включать флагом --threads:on при компиляции. Там свои сборщики мусора, local heap, всякие shared memory и locks, thread safety, специальные shared-модули и хрен знает что ещё. Откуда я про это всё узнал? Правильно, из книги nim in action, форума, stack overflow, телевизора и от соседа, в общем откуда угодно, но не из официальной документации.

Или вот есть т.н. "do notation" — очень хорошо заходит при использовании шаблонов и тд, вообще везде где надо передать callback или просто блок кода. Где про это можно почитать? Ага, в мануале по экспериметальным фичам [9].

Согласитесь, собирать информацию по разным малоинформативным источникам — то ещё удовольствие. Если вы пишете на nim — вам придётся это делать.

На форуме и в github issues проскакивали предложения по улучшению документации, но дело так и не сдвинулось. Мне кажется, не хватает какой-то жёсткой руки, которая скажет "всё, комьюнити, берём лопаты и идём разгребать эту кучу г… ениальных разрозненных кусков текста."

К счастью, я отстрадал своё, поэтому представляю вам список nim-чемпиона

Документация

  • Tutorial 1 [10], Tutorial 2 [11] — с них начинать
  • Nim in action [12] — толковая книжка, которая действительно хорошо объясняет многие аспекты языка, порой намного лучше оф. документации
  • Nim manual [13] — собственно, мануал — описано практически всё, но нет
  • Nim experimental manual [14] — а почему бы, собственно, не продолжить документацию на отдельной страничке?
  • The Index [15] — тут собраны ссылки на всё, то есть вообще всё что можно найти в nim'е. Не нашли нужного в туториалзах и мануале — в индексе точно найдёте.

Уроки и туториалы

  • Nim basics [16] — самые основы для новичков, сложные темы не раскрыты
  • Nim Days [17] — небольшие проекты (live examples)
  • Rosetta Code [18] — очень прикольно сравнивать решение одних и тех же задач на разных ЯП, в т.ч. nim
  • Exercism.io [19] — здесь можно пройти "путь nim", выполняя задания
  • Nim by Example [20]

Помощь

  • Nim forum [21] Возможности форума минимальны, но 1) тут можно найти ответ 2) тут можно задать вопрос, если п.1 не сработал 3) вероятность ответа больше 50% 4) на форуме сидят разработчики языка и активно отвечают. Кстати, форум написан на nim, и поэтому функциональность никакая
  • Nim telegram group [22] — есть возможность задать вопрос и [не]получить ответ.
  • Есть ещё русская телеграм-группа, если вы устали от nim и не хотите о нём ничего слышать — вам туда :) (отчасти шутка)
  • IRC [23] — тут тоже часто отвечают

Playground

  • Nim playground [24] — тут можно запустить программу на nim прямо в браузере
  • Nim docker cross-compiling [25] — тут можно почитать, как запустить докер-образ и скомпилировать программу для разных платформ.

Пакеты

  • nimble.directory [26] — тут собраны все опубликованные пакеты, доступные для установки через пакетный менеджер nimble.
  • Curated list of packages [27] — собранный энтузиастами список более-менее живых пакетов

Переход на nim с других языков

Что нравится

Нет смысла перечислять все возможности языка, но вот некоторые особенности:

Фрактал сложности

Nim предоставляет вам "фрактал сложности". Вы можете писать высокоуровневый код. Можете бодаться с сырыми указателями и радоваться каждой attempt to read from nil. Можете вставлять C-код. Можете писать вставки на ассемблере [31]. Можете писать процедуры (static dispatch). Не хватает — есть "методы" (dynamic dispatch). Ещё? Есть дженерики, и есть дженерики, мимикрирующие под функции. Есть шаблоны (templates) — механизм замены, но не такой блевотный, как в C++ (там это всё ещё просто текстовая замена, или уже что-то поумнее?). Есть макросы, в конце концов — это как IDDQD, они включают режим бога и позволяют работать напрямую с AST и буквально заменять куски синтаксического дерева, или самостоятельно расширять язык как хотите.
То есть на "высоком" уровне вы можете писать хелловорлды и горя не знать, но никто вам не запрещает проводить махинации любой сложности.

Скорость разработки

Кривая обучения — не кривая. Это прямая. Установив nim, вы в первую же минуту запустите ваш первый hello world, а в первый же день вы напишете простую утилиту. Но и через пару месяцев вам будет что изучать. Например, я начинал с процедур, потом мне понадобились методы, через какое-то время мне очень пригодились дженерики, недавно я открыл для себя шаблоны в полной их красе, и при этом я ещё вообще не трогал макросы. Сравнивая с тем же rust или c++, "влиться" в nim гораздо проще.

Package management

Есть package manager под названием nimble, которые умеет устанавливать, удалять, создавать пакеты и подгружать зависимоти. Когда создаёте свой пакет (= проект), в nimble можно прописать разные задачи (при помощи nimscript, который подмножество nim, исполняемый на VM), например, генерацию документации, запуск тестов, копирование ассетов итд. Nimble не только поставит нужные зависимости, но и вообще позволит сконфигурировать рабочее окружение для вашего проекта. То есть nimble — это, грубо говоря, CMake, который написали не извращенцы, а нормальные люди.

Читаемость и выразительность

Внешне nim очень похож на python с type annotations, хотя nim это не python вообще ни разу. Питонистам придётся забыть динамическую типизацию, наследование, декораторы и прочие радости, и вообще перестроить мышление [32]. Не стоит пытаться перенести свой python-опыт в nim, ибо разница слишком большая. Поначалу очень хочется гетерогенных коллекций и миксинов с декораторами. но потом как-то привыкаешь жить в лишениях :)

Вот пример программы на nim:

    type
      NumberGenerator = object of Service  # this service just generates some numbers

      NumberMessage = object of Message
        number: int

    proc run(self: NumberGenerator) =
      if not waitAvailable("calculator"):
        echo "Calculator is unavailable, shutting down"
        return

      for number in 0..<10:
        echo &"Sending number {number}"
        (ref NumberMessage)(number: number).send("calculator")

Модульность

Всё разбито на модули, которые можно как угодно импортировать — импортировать только определённые символы, или все кроме определённых, или все, или ни одного и заставить пользователя указывать полный путь а-ля module.function(), и ещё импортировать под другим именем. Разумеется, всё это многообразие очень пригодится как ещё один агрумент в споре "какой язык программирования лучше", ну а в своём проекте вы будете тихонько везде писать import mymodule и о других вариантах не вспоминать.

Method call syntax

Вызов функции может быть записан по-разному:

    double(2)
    double 2
    2.double()
    2.double

С одной стороны, теперь каждый… пишет как ему нравится (а всем нравится по-разному, разумеется, причём по-разному даже в рамках одного проекта). Но зато все функции могут быть записаны как вызов метода, что очень сильно улучшает читаемость. В питоне может быть такое:

list(set(some_list))  # араб-стайл: читаем справа налево, а ещё можно добавить map и filter и уехать в дурку

Тот же код в nim можно было бы переписать более логично:

some_list.set.list  # читаем слева направо

ООП

ООП хоть и присутствует, но отличается от оного в плюсах и питоне: объекты и методы — разные сущности, и вполне могут существовать в разных модулях. Более того, вы можете написать свои методы для базовых типов вроде int

    proc double(number: int): int =
        number * 2

    echo $2.double()  # prints "4"

С другой стороны, в nim присутствует инкапсуляция (первое правило модуля в nim: никому не рассказывать о идентификаторах без символа звёздочки). Вот пример стандартного модуля:

# sharedtables.nim
type SharedTable*[A, B] = object ## generic hash SharedTable
    data: KeyValuePairSeq[A, B]
    counter, dataLen: int
    lock: Lock

Тип SharedTable* помечен звёздочкой, значит, он "виден" в других модулях и его можно импортировать. Но вот data, counter и lock — приватные члены, и "снаружи" sharedtables.nim они недоступны. Это меня очень обрадовало, когда я решил написать некоторые дополнительные функции для типа SharedTable, навроде len или hasKey, и обнаружил, что у меня нет доступа ни к counter, ни к data, и единственный способ "расширить" SharedTable — написать свой, с бл

Вообще наследование используется намного реже, чем в том же питоне (по личному опыту), потому что есть method call syntax (см. выше) и Object Variants (см ниже). Путь nim — это скорее композиция, а не наследование. Так же и с полиморфизмом: в nim'е есть методы, которые могут быть переопределены в классах-наследниках, но это нужно явно указать при компиляции, используя флаг --multimethods:on. То есть по умолчанию методы не работают, что слегка подталкивает к работе без оных.

Compile-time execution

Const — возможность вычислять что-то на этапе компиляции и "зашивать" это в результирующий бинарник. Это круто и удобно. Вообще в nim особое отношение ко "времени компиляции", даже есть ключевое слово when — это как if, но сравнение идёт на этапе компиляции. Можно написать что-то вроде when defined(linux): echo "Compiling on linux machine" — и это очень удобно, хотя и есть ограничения на то, что можно вытворять на этапе компиляции (например, нельзя делать FFI вызовы).

Reference type

Ref type — аналог shared_ptr в C++, о котором позаботится сборщик мусора. Но можно и самому вызывать сборщик мусора в те моменты, когда это вам удобно. А можно попробовать разные варианты сборщиков мусора. А можно вообще отключить сборщик мусора и использовать обычные указатели.

В идеале, если не использовать сырые указатели и FFI, вы вря ли сможете получить ошибки сегментации. На практике пока без FFI никуда.

Lambdas

Есть анонимные процедуры (aka лямбды в питоне), но в отличие от питона в анонимной процедуре можно использовать несколько statements:

someProc(callback=proc(a: int) -> int = var b = 5*a; result = a)

Exceptions

Есть исключения, их очень неудобно бросать: на python raise ValueError('bad value'), на nim raise newException(ValueError, "bad value). Больше ничего необычного — try, except, finally, всё как у всех. Я, как сторонник исключений, а не кодов ошибок, ликую. Кстати, для функций можно указывать, какие исключения они могут бросить, и компилятор будет это проверять:

proc p(what: bool) {.raises: [IOError, OSError].} =
  if what: raise newException(IOError, "IO")
  else: raise newException(OSError, "OS")

Generics

Дженерики очень выразительные, например, можно ограничивать возможные типы

proc onlyIntOrString[T: int|string](x, y: T) = discard  # только int и string

А можно передавать тип вообще как параметр — выглядит как обычная функция, а на самом деле дженерик:

proc p(a: typedesc; b: a) = discard
# is roughly the same as:
proc p[T](a: typedesc[T]; b: T) = discard
# hence this is a valid call:
p(int, 4)
# as parameter 'a' requires a type, but 'b' requires a value.

Templates

Шаблоны (templates) — что-то вроде шаблонов в C++, только сделанных правильно :) — вы можете безопасно передавать в шаблоны целые блоки кода, и не думать о том, что подстановка что-то испортит в outer коде (но можно, опять же, сделать, чтобы испортила, если очень надо).

Вот пример шаблона app, который в зависимости от значения переменной вызывает один из блоков кода:

template app*(serverCode: untyped, clientCode: untyped) =
    # ...
    case mode
      of client:
        clientCode
      of server:
        serverCode
      else:
        discard

При помощи do я могу передавать целы блоки в шаблон, например:

app do:  # serverCode
  echo "I'm server"
  serverProc()
do:  # clientCode
  echo "I'm client"
  clientProc()

Interactive shell

Если нужно быстро что-то протестировать, то есть возможность вызвать "интерпретатор" или "nim shell" (как если вы запустите python без параметров). Для этого воспользуйтесь командой nim secret или скачайте пакет inim [33].

FFI

FFI — возможность взаимодействовать со сторонними библиотеками на C/C++. К сожалению, для использования внешней библиотеки вы должны написать враппер, объясняющий, откуда и что импортировать. Например:

{.link: "/usr/lib/libOgreMain.so".}
type ManualObjectSection* {.importcpp: "Ogre::ManualObject::ManualObjectSection", bycopy.} = object

Есть инструменты, делающие этот процесс полуавтоматическим:

Что не нравится

Сложность

Слишком много всего. Язык задумывался как минималистичный, но сейчас это очень далеко от правды. Вот например за что мы получили code reordering [36]?!

Избыточность

Много говнища: system.addInt [37] — "Converts integer to its string representation and appends it to result". Мне кажется, это очень удобная функция, я её использую в каждом проекте. Вот ещё интересное: fileExists and existsFile (https://forum.nim-lang.org/t/3636 [38])

Нет унификации

"There's only one way to do smth" — вообще нет:

  • Method call syntax — пиши вызов функции как хочешь
  • fmt vs & [39]
  • camelCase и underscore_notation
  • this и tHiS (спойлер: это одно и то же)
  • function [40] vs procedure [41] vs template [42]

Баги (нет, БАГИ!)

Баги есть, примерно 1400 [43]. Или просто зайдите на форум — там [44] постоянно [45] какие-то [46] баги находят.

Стабильность

В дополнение к предыдущему пункту, v1 подразумевает стабильность, да? И тут на форум залетает создатель языка Araq и говорит: "чуваки, я тут запилил ещё один (шестой) [47] сборщик мусора, он круче, быстрее, молодёжнее, даёт вам shared memory для потоков (ха-ха, а раньше для этого вы страдали и использовали костыли), качайте develop ветку и пробуйте". И все такие "Вау, как круто! А что это значит для простых смертных? Нам теперь опять весь код менять?" [48] Вроде как нет, поэтому я обновляю nim, запускаю новый сборщик мусора --gc:arc и моя программа падает где-то на этапе компиляции c++ кода (т.е. не в nim, а в gcc):

/usr/lib/nim/system.nim:274:77: error: ‘union pthread_cond_t’ has no member named ‘abi’
  274 |   result = x

Великолепно! Теперь вместо того, чтобы писать новый код, я должен чинить старый. Не от этого ли я бежал, когда выбирал nim?

Приятно осознавать, что я не один [49]

Методы и многопоточность

По умолчанию флаги multimethods и threads выключены — вы ведь не собираетесь в 2019 2020 году писать многопоточное приложение с переопределением методов?! А уж как здорово, если ваша библиотека создавалась без учёта потоков, а потом пользователь их включил [50]… Ах да, для наследования есть замечательные прагмы {.inheritable.} и {.base.}, чтобы ваш код не был слишком лаконичен.

Object variants

Вы можете избежать наследования, используя т.н. object variants:

type
  CoordinateSystem = enum
    csCar, # Cartesian
    csCyl, # Cylindrical

  Coordinates = object
    case cs: CoordinateSystem: # cs is the coordinate discriminator
      of csCar:
        x: float
        y: float
        z: float
      of csCyl:
        r: float
        phi: float
        k: float

В зависимости от значения cs, вам будут доступны либо x, y, z поля, либо r, phi и k.

В чём минусы?
Во-первых, память резервируется для всех полей, хотя каждый объект использует только часть полей.
Во-вторых, наследование всё равно более гибкое — всегда можете создать потомка и добавить ещё полей, а в object variant все поля жёстко заданы в одной секции.
В-третьих, что бесит больше всего — нельзя "переиспользовать" [51] поля в разных типах:

type

  # The 3 notations refer to the same 3-D entity, and some coordinates are shared
  CoordinateSystem = enum
    csCar, # Cartesian    (x,y,z)
    csCyl, # Cylindrical  (r,φ,z)

  Coordinates = object
    case cs: CoordinateSystem: # cs is the coordinate discriminator
      of csCar:
        x: float
        y: float
        z: float  # z already defined here
      of csCyl:
        r: float
        phi: float
        z: float  # fails to compile due to redefinition of z

Do notation

Просто процитирую [9]:

  • do with parentheses is an anonymous proc
  • do without parentheses is just a block of code
    Одно выражение означает разные вещи ¯_(ツ)_/¯

Когда что использовать

Итак, у нас есть функции, процедуры, дженерики, мультиметоды, шаблоны и макросы. Когда лучше использовать шаблон, а когда процедуру? Шаблон или дженерик? Функция или процедура? Так, а макросы? Я думаю, вы поняли.

Custom pragma

В питоне есть декораторы, которые можно применять хоть к классам, хоть к функциям.
В nim для этого есть прагмы. И вот что:

  • Вы можете написать свою прагму, которая будет декорировать процедуру:
    proc fib(n : int) : int {.cached.} =
    # do smth
  • Вы не можете [52] написать свою прагму, которая будет декорировать тип (=класс).

Nimble

Что мертво — умереть не может. В nimble куча проектов, которые уже давно не обновлялись (а в nim это смерти подобно) — и их не убирают. Никто за этим не следит. Понятно, обратная совместимость, "нельзя просто взять и удалить пакет из репы", но всё же...

Дырявые абстракции

Есть такой закон дырявых абстракций [53] — вы используете какую-то абстракцию, но рано или поздно вы обнаружете в ней "дыру", которая приведёт вас на уровень ниже. Nim — это абстракция над C и C++, и рано или поздно вы туда "провалитесь". Спорим, вам там не понравится?

Error: execution of an external compiler program 'g++ -c  -w -w -fpermissive -pthread   -I/usr/lib/nim -I/home/user/c4/systems/network -o 
/home/user/.cache/nim/enet_d/@m..@s..@s..@s..@s..@s..@s.nimble@spkgs@smsgpack4nim-0.3.0@smsgpack4nim.nim.cpp:6987:136: note:   initializing argument 2 of ‘void unpack_type__k2dhaoojunqoSwgmQ9bNNug(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA*, NU&)’
 6987 | N_LIB_PRIVATE N_NIMCALL(void, unpack_type__k2dhaoojunqoSwgmQ9bNNug)(tyObject_MsgStreamcolonObjectType___kto5qgghQl207nm2KQZEDA* s, NU& val) { nimfr_("unpack_type", "/home/user/.nimble/pkgs/msgpack4nim-0.3.0/msgpack4nim.nim");
      |                                                                                                                       

/usr/bin/ld: /home/user/.cache/nim/enet_d/stdlib_dollars.nim.cpp.o: in function `dollar___uR9bMx2FZlD8AoPom9cVY9ctA(tyObject_ConnectMessage__e5GUVMJGtJeVjEZUTYbwnA*)':
stdlib_dollars.nim.cpp:(.text+0x229): undefined reference to `resizeString(NimStringDesc*, long)'
/usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x267): undefined reference to `resizeString(NimStringDesc*, long)'
/usr/bin/ld: stdlib_dollars.nim.cpp:(.text+0x2a2): undefined reference to `resizeString(NimStringDesc*, long)'

Итак

Я тупой программист. Я не хочу знать, как работает GC, что там и как линкуется, куда кэшируется и как убирается мусор. Это как с машиной — я в принципе знаю, как она устроена, немного про сход-развал, немного про коробку передач, масло там надо заливать и прочее, но вообще я просто хочу сесть и ехать (причём быстро) на вечеринку. Машина — не цель, а средство достижения цели. Если она сломается — я не хочу лезть в капот, а просто отвезу её на сервис (в смысле, открою issue на гитхабе), и было бы здорово, если бы чинили её быстро.

Nim должен был стать такой машиной. Отчасти он и стал, но в то же время, когда я мчусь на этой машине по хайвею, у меня отваливается колесо, а заднее зеркало показывает вперёд. За мной бегут инженеры и на ходу что-то приделывают ("теперь с этим новым спойлером ваша машина ещё быстрее"), но от этого у меня отваливается багажник. И знаете что? Мне всё равно чертовски нравится эта машина, ведь это лучшая из всех машин, что я видел.

Автор: kesn

Источник [54]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/341433

Ссылки в тексте:

[1] platoff: https://habr.com/ru/users/platoff/

[2] Как я нашел лучший в мире язык программирования. Часть 1: https://habr.com/ru/post/259831/

[3] Как я нашел лучший в мире язык программирования. Часть 2: https://habr.com/ru/post/259841/

[4] Как я нашел лучший в мире язык программирования. Часть Йо (2.72): https://habr.com/ru/post/260149/

[5] Nim in action: https://www.manning.com/books/nim-in-action

[6] Status: https://github.com/status-im

[7] вышла версия 1.0: https://nim-lang.org/blog/2019/09/23/version-100-released.html

[8] Вот раздел официальной документации про потоки: https://nim-lang.org/docs/manual.html#threads

[9] мануале по экспериметальным фичам: https://nim-lang.org/docs/manual_experimental.html#do-notation

[10] Tutorial 1: https://nim-lang.org/docs/tut1.html

[11] Tutorial 2: https://nim-lang.org/docs/tut2.html

[12] Nim in action: https://book.picheta.me/

[13] Nim manual: https://nim-lang.org/docs/manual.html

[14] Nim experimental manual: https://nim-lang.org/docs/manual_experimental.html

[15] The Index: https://nim-lang.org/docs/theindex.html

[16] Nim basics: https://narimiran.github.io/nim-basics/

[17] Nim Days: https://xmonader.github.io/nimdays/

[18] Rosetta Code: http://rosettacode.org/wiki/Category:Nim

[19] Exercism.io: https://exercism.io/my/tracks/nim

[20] Nim by Example: https://nim-by-example.github.io/getting_started/

[21] Nim forum: https://forum.nim-lang.org

[22] Nim telegram group: https://t.me/nim_lang

[23] IRC: http://irc://freenode.net/nim

[24] Nim playground: http://play.nim-lang.org

[25] Nim docker cross-compiling: https://forum.nim-lang.org/t/5569

[26] nimble.directory: https://nimble.directory/

[27] Curated list of packages: https://github.com/nim-lang/Nim/wiki/Curated-Packages

[28] Imports in Nim: https://narimiran.github.io//2019/07/01/nim-import.html

[29] Nim for python programmers: https://github.com/nim-lang/Nim/wiki/Nim-for-Python-Programmers

[30] Nim for C programmers: https://github.com/nim-lang/Nim/wiki/Nim-for-C-programmers

[31] вставки на ассемблере: https://nim-lang.org/docs/manual.html#statements-and-expressions-assembler-statement

[32] мышление: http://www.braintools.ru

[33] inim: https://github.com/AndreiRegiani/INim

[34] c2nim: https://github.com/nim-lang/c2nim

[35] nimterop: https://github.com/nimterop/nimterop

[36] code reordering: https://nim-lang.org/docs/manual.html#scope-rules-code-reordering

[37] system.addInt: https://nim-lang.org/docs/system.html#addInt%2Cstring%2Cint64

[38] https://forum.nim-lang.org/t/3636: https://forum.nim-lang.org/t/3636

[39] fmt vs &: https://nim-lang.org/docs/strformat.html#fmt-vsdot-amp

[40] function: https://nim-lang.org/docs/manual.html#procedures-func

[41] procedure: https://nim-lang.org/docs/manual.html#procedures

[42] template: https://nim-lang.org/docs/manual.html#templates

[43] примерно 1400: https://github.com/nim-lang/Nim/issues

[44] там: https://forum.nim-lang.org/t/5620

[45] постоянно: https://forum.nim-lang.org/t/5615

[46] какие-то: https://forum.nim-lang.org/t/5590

[47] ещё один (шестой): https://nim-lang.org/docs/gc.html#garbage-collector-options

[48] "Вау, как круто! А что это значит для простых смертных? Нам теперь опять весь код менять?": https://forum.nim-lang.org/t/5734

[49] не один: https://forum.nim-lang.org/t/5746

[50] пользователь их включил: https://forum.nim-lang.org/t/5321#33482

[51] нельзя "переиспользовать": https://forum.nim-lang.org/t/5729

[52] не можете: https://forum.nim-lang.org/t/3705

[53] закон дырявых абстракций: https://en.wikipedia.org/wiki/Leaky_abstraction

[54] Источник: https://habr.com/ru/post/462577/?utm_source=habrahabr&utm_medium=rss&utm_campaign=462577