Добавить объявление

Swift делает поведение nonisolated функций предсказуемым

Swift Concurrency продолжает эволюционировать, и одно из ключевых изменений - пересмотр логики работы nonisolated асинхронных функций. Сейчас они ведут себя противоречиво: синхронные nonisolated методы выполняются в контексте вызывающей стороны (например на акторе), а асинхронные - всегда переключаются на внешний executor, что вызывает неожиданные ошибки типов и усложняет архитектуру.

Суть проблемы - разрыв между ожиданием и реальностью:


Когда вы вызываете nonisolated асинхронный метод из актора, компилятор вынужден применять строгие Sendable-проверки ко всем аргументам, даже если метод по своей природе не должен покидать контекст актора. Это приводит к ложным ошибкам, особенно в библиотечном коде, где автор API может не предусмотреть такой сценарий. Даже стандартная библиотека Swift Concurrency сталкивалась с этой проблемой.

Решение - явное управление изоляцией через новые модификаторы:


Предложение SE-0461 вводит два новых инструмента для точного контроля:

  • nonisolated(nonsending) - явно указывает, что асинхронная функция должна выполняться в контексте вызывающего актора (унаследовать его изоляцию). Это поведение становится предсказуемым и аналогичным синхронным функциям.

  • @concurrent - явно указывает, что функция должна переключаться на внешний executor (это старое поведение по умолчанию). Используется в случаях, когда необходимо гарантированно выйти из текущего актора для выполнения работы, например, для избежания его блокировки или для фоновых операций.


class NotSendable {
nonisolated(nonsending)
func performAsync() async {
// Работает в контексте вызывающего актора
}
}

actor MyActor {
let item = NotSendable()

func call() async {
item.performSync() // OK
await item.performAsync() // Теперь тоже OK, ошибки типов нет!
}
}

Макрос #isolation и работа с замыканиями:


Для продвинутых сценариев, таких как создание оберток (например withResource), расширяется функциональность макроса #isolation. Теперь он может корректно передавать контекст изоляции в nonisolated(nonsending) функции, что делает написание высокоуровневых асинхронных API значительно проще и безопаснее.

Важное уточнение: неструктурированные задачи (Task { }), созданные внутри таких функций, по-прежнему не наследуют изоляцию, что соответствует принципам предсказуемости.

Практический смысл - баланс между безопасностью и удобством:


Это изменение - не просто синтаксический сахар. Оно решает фундаментальную проблему: делает поведение nonisolated функций последовательным и интуитивно понятным. Разработчики получают явный контроль:

  • Хотите, чтобы функция работала в том же контексте и не требовала Sendable? Используйте nonisolated(nonsending).

  • Нужно гарантированно переключиться для параллельной работы? Явно укажите @concurrent.

Вывод:


Swift продолжает движение к модели, где безопасность от гонок данных достигается не через неявные, сложные правила, а через явные и понятные инструменты, дающие разработчику полный контроль над изоляцией. SE-0461 закрывает один из последних существенных пробелов в этой картине, делая асинхронное программирование в Swift более последовательным, предсказуемым и, как следствие - доступным для написания сложных, но корректных приложений.
10.02.2026 9 557