Что именно происходит с аргументом при передаче в функцию через параметр inout?
Параметр inout предоставляет функции временный эксклюзивный доступ к исходной переменной, а не независимую копию для обычной работы. Функция может изменить это значение, и после завершения вызова изменения становятся видны вызывающему коду.
Концептуально Swift описывает это как copy-in/copy-out: значение передаётся внутрь функции, затем результат записывается обратно. Однако компилятор может оптимизировать реализацию, поэтому буквального копирования в памяти может не происходить.
Swift сочетает изменяемость значений с предсказуемой семантикой значений. Для этого языку нужен механизм, позволяющий функции временно изменять переменную вызывающего кода без превращения всех параметров в неявные ссылки.
inout решает эту задачу вместе с правилом эксклюзивного доступа к памяти. Благодаря этому Swift может обнаруживать потенциально конфликтующие одновременные чтения и записи ещё на этапе компиляции или во время выполнения.
Обычный параметр функции получает значение и не должен изменять переменную, из которой это значение было взято. Если разрешить функции произвольно менять исходные данные, появятся скрытые побочные эффекты и неоднозначность: несколько частей программы могут одновременно читать и изменять один объект.
inout явно обозначает намерение изменить переданную переменную. На время вызова исходная переменная должна быть доступна для записи и не должна одновременно использоваться другим конфликтующим доступом.
При вызове функции с inout Swift временно передаёт функции доступ к хранилищу переменной. Функция работает с параметром как с изменяемым значением, а после завершения доступа обновлённое значение возвращается в исходную переменную.
Символ & указывает, что передаётся не результат вычисления, а сама изменяемая переменная. Константу, литерал или произвольное выражение передать как inout нельзя: у них нет подходящего изменяемого хранилища.
Доступ является эксклюзивным: пока функция владеет inout-доступом, другой конфликтующий доступ к той же памяти запрещён. Это защищает от ситуаций, когда одна операция записывает значение, одновременно используя устаревшее или частично изменённое состояние.
inout не является обычной ссылкой, которую можно сохранить после возврата из функции. Это ограниченный по времени доступ, действующий только во время вызова. Кроме того, передача значения через inout не превращает тип-значение в ссылочный тип.
Для экземпляра класса важно различать ссылку и объект. Если через inout передаётся переменная, содержащая ссылку на класс, функция получает возможность изменить саму переменную-ссылку, например заменить её другим экземпляром. При этом изменение свойств уже доступного экземпляра может выполняться и без inout, поскольку несколько переменных могут ссылаться на один объект.
Главный компромисс — явная мутация делает контракт функции понятнее, но ограничивает способ вызова: требуется изменяемая переменная и соблюдение правил эксклюзивного доступа. Внутренняя оптимизация может устранить фактическое копирование, однако наблюдаемое поведение должно соответствовать модели copy-in/copy-out.
В модуле обработки платежа функция должна нормализовать сумму и вернуть обновлённое значение вызывающему коду. Рассматривались три варианта: вернуть новое значение, хранить результат в общем изменяемом состоянии или использовать inout.
Возврат нового значения наиболее прозрачен и проще тестируется, но требует явно присвоить результат обратно. Общее состояние скрывает поток данных и усложняет тестирование. inout позволяет выразить изменение существующей переменной напрямую, но требует контроля эксклюзивного доступа и аккуратного API-дизайна.
Для локальной операции выбрали inout, потому что функция должна изменить ровно одну переданную переменную, а такой побочный эффект виден в её сигнатуре. Для публичного API, где важна максимальная композиция и отсутствие мутации, предпочтительнее было бы вернуть новое значение.
Можно ли передать в inout константу или результат выражения?
Нет. inout требует изменяемого хранилища, потому что после завершения функции результат должен быть записан обратно. Константа запрещена по смыслу, а результат выражения обычно не является самостоятельной переменной, которую можно обновить.
Поэтому функция с inout принимает переменные, свойства с допустимой записью и другие подходящие изменяемые места хранения, но не литералы и не произвольные вычисления.
Почему нельзя одновременно передать одну переменную в два параметра inout?
Это нарушило бы эксклюзивность доступа: функция получила бы два независимых изменяемых доступа к одному хранилищу. Порядок чтений и записей тогда мог бы влиять на результат, а компилятор не обязан принимать такую неоднозначность.
Даже если программист уверен, что операции не конфликтуют, правило применяется к периоду действия доступов, а не к предполагаемому фактическому порядку отдельных инструкций. Обычно конфликт устраняют разделением операции на последовательные шаги или передачей одного значения вместо двух доступов.
Является ли inout ссылочным параметром?
Нет, это не постоянная ссылка и не способ сохранить адрес переменной. inout предоставляет временный доступ только на время вызова, после чего функция теряет возможность обращаться к исходному хранилищу.
В реализации компилятор может использовать адрес напрямую или применить копирование, но это деталь оптимизации. Семантически важно другое: функция получает право изменить исходную переменную, а вызывающий код видит итоговое значение после возврата.
Если сама переменная содержит ссылку на экземпляр класса, inout относится к переменной-ссылке. Он позволяет заменить эту ссылку, тогда как изменение состояния объекта через уже имеющуюся ссылку связано с ссылочной семантикой класса, а не с механизмом inout.