← На главную

Ruby-приём: как отличить отсутствие аргумента от nil

02.11.2026 20:32 · hackernews

Инженер Aha! описал небольшой приём Ruby для методов, которые одновременно работают как getter и setter. Обычная схема принимает необязательный аргумент со значением nil: если аргумент истинный, метод сохраняет его, иначе возвращает текущее значение. Пока настройка принимает только содержательные значения, это работает, но false и nil ломают контракт. Первый не должен означать чтение, а второй может быть отдельной командой очистить настройку.

Проверка value.nil? проблему не решает. Ruby одинаково передаёт в метод явный nil и отсутствие аргумента, если у параметра задано значение по умолчанию nil. Распространённое решение — создать отдельный sentinel-объект, заморозить его и сравнивать аргумент с ним через equal?. Тогда любой пользовательский объект, включая nil и false, можно отличить от маркера «аргумент не передан», но для простого метода приходится заводить дополнительную константу.

Предложенный приём использует выражение по умолчанию с присваиванием: параметр получает значение (nothing_provided = true). Ключевое свойство Ruby состоит в том, что такое выражение вычисляется только при пропущенном аргументе. Если вызвать метод без значения, присваивание выполнится, локальная переменная станет true, а метод вернёт сохранённую настройку. Само присваивание одновременно даёт параметру значение true, хотя в этой ветке оно не используется.

При вызове с любым аргументом Ruby пропускает всё выражение по умолчанию. Имя nothing_provided всё равно распознаётся как локальная переменная, но поскольку присваивание не выполнялось, её значение остаётся nil. Ветка чтения не запускается, а полученный аргумент сохраняется. Поэтому четыре вызова — с true, false, nil и вообще без аргумента — имеют разные и однозначные результаты.

Автор обнаружил этот подход в реализации index_with в Rails. Там признак no_default позволяет отличить вызов без значения по умолчанию, когда метод возвращает Enumerator, от вызова с конкретным default, включая nil. При наличии блока значения формируются через yield, а без него используется переданный default. Приём внедрил участник byroot в Rails PR #44611.

Практический смысл приёма — не заменять семантику «ничего не передано» значением nil, если это разные состояния интерфейса метода. Вместо отдельного объекта-маркера используется сам момент вычисления default expression. Решение компактно, хотя требует понимания порядка вычисления аргументов Ruby.

Читать оригинал →