Rust: [Стабилизация] async / await MVP

Созданный на 26 июн. 2019  ·  58Комментарии  ·  Источник: rust-lang/rust

Цель стабилизации: 1.38.0 (бета-версия 15.08.2019)

Управляющее резюме

Это предложение по стабилизации минимально жизнеспособной функции async / await, которая включает:

  • async аннотации к функциям и блокам, из-за чего их оценка откладывается и вместо этого оценивается в будущем.
  • Оператор await , действительный только в контексте async , который принимает в качестве аргумента будущее и заставляет внешнее будущее, в котором оно находится, передавать управление до тех пор, пока ожидаемое будущее не завершится.

Связанные предыдущие обсуждения

RFC:

Проблемы с отслеживанием:

Стабилизации:

Достигнуты важные решения

  • Будущее, которое оценивает асинхронное выражение, строится из его начального состояния, не выполняя никакого основного кода перед уступкой.
  • Синтаксис для асинхронных функций использует «внутренний» тип возвращаемого значения (тип, который соответствует внутреннему выражению return ), а не «внешний» тип возвращаемого значения (будущий тип, который оценивается при вызове функции)
  • Синтаксис оператора await - это "постфиксный точечный синтаксис" expression.await , в отличие от более распространенного await expression или другого альтернативного синтаксиса.

Стабилизация блокировки работ по реализации

  • [x] async fns должен иметь возможность принимать несколько жизней # 56238
  • Размер генераторов [x] не должен расти экспоненциально # 52924
  • [] Минимальная жизнеспособная документация для функции async / await
  • [] Достаточные тесты компилятора поведения

Будущая работа

  • Async / await в контекстах no-std: async и await настоящее время полагаются на работу TLS. Это проблема реализации, которая не является частью дизайна, и, хотя она не блокирует стабилизацию, предполагается, что в конечном итоге она будет решена.
  • Асинхронные функции высшего порядка: async как модификатор для закрывающих литералов здесь не стабилизируется. Требуется больше проектных работ в отношении захвата и абстракции асинхронных замыканий со сроками жизни.
  • Методы асинхронных признаков: это требует значительной работы по проектированию и реализации, но это очень желательная функция.
  • Потоковая обработка: парой признака Future в библиотеке futures является признак Stream, асинхронный итератор. Интеграция поддержки управления потоками в std и язык - желательная долгосрочная функция.
  • Оптимизация представлений генераторов: можно проделать больше работы по оптимизации представления генераторов, чтобы сделать их более точными по размеру. Мы убедились, что это чисто проблема оптимизации и не имеет семантического значения.

Фон

Обработка неблокирующего ввода-вывода очень важна для разработки высокопроизводительных сетевых сервисов, что является целевым вариантом использования Rust, вызывающим значительный интерес со стороны производственных пользователей. По этой причине решение, позволяющее сделать эргономичное и выполнимое написание сервисов с использованием неблокирующего ввода-вывода, долгое время было целью Rust. Функция async / await - это кульминация этих усилий.

До версии 1.0 в Rust была система greenthreading, в которой Rust предоставлял альтернативный примитив многопоточности на уровне языка, построенный поверх неблокирующего ввода-вывода. Однако эта система вызвала несколько проблем: наиболее важно введение языковой среды выполнения, которая влияла на производительность даже программ, которые ее не использовали, значительно увеличивая накладные расходы на FFI, а также наличие нескольких серьезных нерешенных проблем проектирования, связанных с реализацией стеков greenthread. .

После удаления зеленых нитей участники проекта Rust начали работу над альтернативным решением, основанным на абстракции фьючерсов. Иногда также называемые обещаниями, фьючерсы были очень успешными в других языках в качестве абстракции на основе библиотек для неблокирующего ввода-вывода, и было известно, что в долгосрочной перспективе они хорошо отображаются на синтаксис async / await, который может сделать их лишь незначительно менее удобными, чем полностью невидимая система пропитки.

Основным прорывом в развитии абстракции Future стало введение модели Futures, основанной на опросах. В то время как другие языки используют модель, основанную на обратном вызове, в которой само будущее отвечает за планирование выполнения обратного вызова после его завершения, Rust использует модель на основе опроса, в которой исполнитель отвечает за опрос будущего до завершения, а future просто информирует исполнителя о том, что он готов к дальнейшему прогрессу, используя абстракцию Waker. Эта модель хорошо себя зарекомендовала по нескольким причинам:

  • Это позволило rustc компилировать фьючерсы для конечных автоматов, которые имели минимальные накладные расходы на память, как с точки зрения размера, так и с точки зрения косвенности. Это дает значительные преимущества в производительности по сравнению с подходом, основанным на обратном вызове.
  • Это позволяет таким компонентам, как исполнитель и реактор, существовать как библиотечные API, а не как часть языковой среды выполнения. Это позволяет избежать глобальных затрат, влияющих на пользователей, которые не используют эту функцию, и позволяет пользователям легко заменять отдельные компоненты своей системы выполнения, вместо того, чтобы требовать от нас принятия решения о черном ящике для них на уровне языка.
  • Он также делает все примитивы параллелизма библиотеками, вместо того, чтобы вносить параллелизм в язык через семантику операторов async и await. Это делает параллелизм более понятным и наглядным благодаря исходному тексту, который должен использовать идентифицируемый примитив параллелизма для введения параллелизма.
  • Это позволяет отмену без накладных расходов, позволяя отбрасывать исполняемые фьючерсы до их завершения. Возможность бесплатной отмены всех фьючерсов дает преимущества производительности и ясности кода для исполнителей и примитивов параллелизма.

(Последние два пункта также были определены как источник путаницы для пользователей, переходящих с других языков, на которых они не соответствуют действительности, и приносящих с собой ожидания от этих языков. Однако оба эти свойства являются неизбежными свойствами модели на основе опросов. который имеет другие очевидные преимущества и, по нашему мнению, станет полезным, если пользователи их поймут.)

Однако модель, основанная на опросах, страдала от серьезных эргономических проблем при взаимодействии со ссылками; по сути, ссылки в точках доходности приводили к неразрешимым ошибкам компиляции, хотя они и должны быть безопасными. В результате получился сложный, зашумленный код, полный дуг, мьютексов и замыканий перемещений, ни один из которых не был строго необходим. Даже если оставить эту проблему в стороне, без примитива языкового уровня, фьючерсы страдали от принуждения пользователей к стилю написания сильно вложенных обратных вызовов.

По этой причине мы использовали синтаксический сахар async / await с поддержкой нормального использования ссылок в точках доходности. После введения абстракции Pin которая сделала ссылки на точки доходности безопасными для поддержки, мы разработали собственный синтаксис async / await, который компилирует функции в наши фьючерсы на основе опросов, позволяя пользователям получать преимущества в производительности асинхронного ввода-вывода с Futures при написании кода, который очень похож на стандартный императивный код. Эта последняя особенность является предметом настоящего отчета о стабилизации.

описание функции async / await

Модификатор async

Ключевое слово async может применяться в двух местах:

  • Перед выражением блока.
  • Перед свободной функцией или ассоциированной функцией в собственном impl.

_ (Другие места для асинхронных функций - например, закрывающие литералы и методы черт будут развиваться и стабилизироваться в будущем.) _

Модификатор async регулирует изменяемый элемент, «превращая его в будущее». В случае блока, блок оценивается с точки зрения будущего его результата, а не его результата. В случае функции вызовы этой функции возвращают будущее ее возвращаемого значения, а не возвращаемое значение. Код внутри элемента, измененного модификатором async, называется находящимся в контексте async.

Модификатор async выполняет эту модификацию, заставляя элемент вместо этого оцениваться как чистый конструктор будущего, принимая аргументы и захваты как поля будущего. Каждая точка ожидания рассматривается как отдельный вариант этого конечного автомата, и метод будущего «опроса» продвигает будущее через эти состояния на основе преобразования кода, написанного пользователем, до тех пор, пока в конечном итоге он не достигнет своего конечного состояния.

Модификатор async move

Подобно замыканиям, асинхронные блоки могут захватывать переменные в окружающей области видимости в состояние будущего. Как и замыкания, эти переменные по умолчанию фиксируются по ссылке. Однако вместо этого они могут быть захвачены по значению с помощью модификатора move (точно так же, как замыкания). async предшествует move , что делает эти блоки async move { } блоками.

Оператор await

В асинхронном контексте новое выражение может быть сформировано путем объединения выражения с оператором await , используя следующий синтаксис:

expression.await

Оператор ожидания может использоваться только внутри асинхронного контекста, а тип выражения, к которому он применяется, должен реализовывать черту Future . Выражение await оценивается как выходное значение будущего, к которому оно применяется.

Оператор await передает управление будущим, которое оценивает асинхронный контекст, до тех пор, пока будущее, к которому он применяется, не завершится. Эту операцию выдачи управления нельзя записать в поверхностном синтаксисе, но если бы она могла (используя синтаксис YIELD_CONTROL! в этом примере), десугарирование await выглядело бы примерно так:

loop {
    match $future.poll(&waker) {
        Poll::Ready(value)  => break value,
        Poll::Pending       => YIELD_CONTROL!,
    }
}

Это позволяет вам дождаться завершения оценки фьючерсов в асинхронном контексте, перенаправляя передачу управления через Poll::Pending наружу во внешний асинхронный контекст, в конечном итоге - к исполнителю, на котором было создано будущее.

Основные моменты принятия решения

Сдаться немедленно

Наши асинхронные функции и блоки «выходят немедленно» - их построение - это чистая функция, которая переводит их в начальное состояние до выполнения кода в теле асинхронного контекста. Ни один из основных кодов не выполняется, пока вы не начнете опрос этого будущего.

Это отличается от многих других языков, в которых вызовы триггера асинхронной функции запускаются немедленно. В этих других языках async по своей сути является параллельной конструкцией: когда вы вызываете асинхронную функцию, она запускает другую задачу, чтобы начать выполнение одновременно с вашей текущей задачей. В Rust, однако, фьючерсы не выполняются параллельно.

Мы могли бы сделать так, чтобы асинхронные элементы выполнялись до первой точки ожидания, когда они были созданы, вместо того, чтобы делать их чистыми. Однако мы решили, что это больше сбивает с толку: выполняется ли код во время построения будущего или опроса, это будет зависеть от размещения первого await в теле. Проще подумать о том, чтобы весь код выполнялся во время опроса, а не во время построения.

Ссылка:

Синтаксис возвращаемого типа

Синтаксис наших асинхронных функций использует «внутренний» тип возвращаемого значения, а не «внешний» тип возвращаемого значения. То есть они говорят, что возвращают тип, который они в конечном итоге оценивают, вместо того, чтобы говорить, что они возвращают будущее этого типа.

На одном уровне это решение о том, какой вид ясности предпочтительнее: поскольку подпись также включает аннотацию async , тот факт, что они возвращают будущее, явно указывается в подписи. Однако пользователям может быть полезно увидеть, что функция возвращает будущее, не обращая внимания на ключевое слово async. Но это также похоже на шаблон, поскольку информация также передается с помощью ключевого слова async .

Что действительно изменило чашу весов для нас, так это вопрос о пожизненной элизии. "Внешний" тип возврата любой асинхронной функции - impl Future<Output = T> , где T - это внутренний тип возврата. Однако это будущее также фиксирует время жизни любых входных аргументов само по себе: это противоположно значению по умолчанию для impl Trait, которое, как предполагается, не фиксирует какие-либо входные времена жизни, если вы их не укажете. Другими словами, использование внешнего возвращаемого типа будет означать, что асинхронные функции никогда не выиграют от исключения времени жизни (если мы не сделали что-то еще более необычное, например, чтобы правила исключения времени жизни работали по-разному для асинхронных функций и других функций).

Мы решили, что, учитывая, насколько многословным и откровенно запутанным будет на самом деле писать внешний тип возвращаемого значения, не стоит дополнительно сигнализировать о том, что это возвращает будущее, чтобы потребовать от пользователей его написания.

Заказ деструктора

Порядок деструкторов в асинхронных контекстах такой же, как и в неасинхронных контекстах. Точные правила здесь немного сложны и выходят за рамки, но в целом значения уничтожаются, когда они выходят за рамки. Однако это означает, что они продолжают существовать в течение некоторого времени после использования, пока не будут очищены. Если это время включает операторы ожидания, эти элементы должны быть сохранены в состоянии будущего, чтобы их деструкторы могли запускаться в нужное время.

В качестве оптимизации размера будущих состояний мы могли бы вместо этого переупорядочить деструкторы, чтобы они были более ранними в некоторых или всех контекстах (например, неиспользуемые аргументы функции могут быть немедленно отброшены, а не сохранены в состоянии будущего). Однако мы решили этого не делать. Порядок деструкторов может быть сложной и запутанной проблемой для пользователей и иногда очень важен для семантики программы. Мы решили отказаться от этой оптимизации в пользу обеспечения максимально простого упорядочивания деструкторов - того же порядка деструкторов, если бы все ключевые слова async и await были удалены.

(Когда-нибудь мы можем быть заинтересованы в поиске способов пометить деструкторы как чистые и переупорядочиваемые. Это будущая проектная работа, которая также имеет последствия, не связанные с async / await.)

Ссылка:

Синтаксис оператора ожидания

Одним из основных отклонений от функций async / await других языков является синтаксис нашего оператора await. Это было предметом огромного количества дискуссий, больше, чем любое другое решение, которое мы приняли при разработке Rust.

С 2015 года в Rust есть постфиксный оператор ? для эргономичной обработки ошибок. Задолго до 1.0 в Rust также был постфиксный оператор . для доступа к полям и вызовов методов. Поскольку основным вариантом использования фьючерсов является выполнение некоторого рода операций ввода-вывода, подавляющее большинство фьючерсов оцениваются как Result с некоторыми
своего рода ошибка. Это означает, что на практике почти каждая операция ожидания выполняется либо с ? либо с вызовом метода после нее. Учитывая стандартный приоритет для операторов префикса и постфикса, это привело бы к тому, что почти все операторы ожидания были записаны в (await future)? , что мы считали крайне неэргономичным.

Поэтому мы решили использовать постфиксный синтаксис, который очень хорошо сочетается с операторами ? и . . После рассмотрения множества различных синтаксических опций мы решили использовать оператор . за которым следует ключевое слово await.

Ссылка:

Поддержка как однопоточных, так и многопоточных исполнителей

Rust разработан, чтобы упростить написание параллельных и параллельных программ без дополнительных затрат на людей, пишущих программы, которые выполняются в одном потоке. Важно иметь возможность запускать асинхронные функции как на однопоточных, так и на многопоточных исполнителях. Ключевое различие между этими двумя вариантами использования заключается в том, что многопоточные исполнители будут связывать фьючерсы, которые они могут создать, с помощью Send , а однопоточные исполнители - нет.

Подобно существующему поведению синтаксиса impl Trait , асинхронные функции «пропускают» автоматические свойства будущего, которые они возвращают. То есть, помимо наблюдения за тем, что внешний тип возвращаемого значения является будущим, вызывающий может также определить, является ли этот тип Send или Sync, на основе исследования его тела. Это означает, что когда тип возвращаемого значения async fn запланирован для многопоточного исполнителя, он может проверить, безопасно ли это. Однако тип не обязательно должен быть Send, поэтому пользователи однопоточных исполнителей могут воспользоваться преимуществами более производительных однопоточных примитивов.

Были некоторые опасения, что это не сработает, когда асинхронные функции будут расширены в методы, но после некоторого обсуждения было определено, что ситуация не будет существенно отличаться.

Ссылка:

Известные блокираторы стабилизации

Размер штата

Выпуск: # 52924

Способ асинхронного преобразования в конечный автомат в настоящее время реализован не совсем оптимально, в результате чего состояние становится намного больше, чем необходимо. Это возможно, поскольку размер состояния на самом деле увеличивается сверхлинейно, чтобы вызвать переполнение стека в реальном стеке, когда размер состояния становится больше, чем размер обычного системного потока. Улучшение этого кодогенератора так, чтобы размер был более разумным, по крайней мере, не настолько плохим, чтобы вызывать переполнение стека при нормальном использовании, - это исправление ошибки блокировки.

Множественные времена жизни в асинхронных функциях

Выпуск: # 56238

Асинхронные функции должны иметь возможность иметь несколько значений времени жизни в своей сигнатуре, все из которых «фиксируются» в будущем, когда функция будет оцениваться при ее вызове. Однако текущее понижение до impl Future внутри компилятора не поддерживает несколько значений времени жизни ввода; необходим более глубокий рефакторинг, чтобы сделать
эта работа. Поскольку пользователи с большой вероятностью будут писать функции с несколькими (возможно, все исключенными) временами жизни ввода, это исправление блокирующей ошибки.

Другие проблемы с блокировкой:

Этикетка

Будущая работа

Все это известные и очень приоритетные расширения для MVP, над которыми мы намерены приступить, как только мы отправим начальную версию async / await.

Асинхронные закрытия

В первоначальном RFC мы также поддерживали модификатор async в качестве модификатора для литералов замыкания, создавая анонимные асинхронные функции. Однако опыт использования этой функции показал, что есть еще ряд вопросов, которые необходимо решить, прежде чем мы почувствуем себя комфортно, стабилизируя этот вариант использования:

  1. Природа захвата переменных усложняется при асинхронных замыканиях и требует некоторой синтаксической поддержки.
  2. Абстрагирование от асинхронных функций со временем жизни ввода в настоящее время невозможно и может потребоваться поддержка некоторого дополнительного языка или библиотеки.

Поддержка без ЗППП

Текущая реализация оператора await требует, чтобы TLS передавал пробуждение вниз, когда он опрашивает внутреннее будущее. По сути, это «взлом», позволяющий как можно скорее заставить синтаксис работать в системах с TLS. В долгосрочной перспективе мы не намерены использовать TLS и предпочли бы передавать пробуждение в качестве обычного аргумента функции. Однако это требует более глубоких изменений в коде генерации конечного автомата, чтобы он мог обрабатывать прием аргументов.

Хотя мы не блокируем внедрение этого изменения, мы считаем его высокоприоритетным, поскольку оно предотвращает использование async / await в системах без поддержки TLS. Это чисто проблема реализации: ничто в дизайне системы не требует использования TLS.

Асинхронные методы черт

В настоящее время мы не разрешаем использовать асинхронные функции или методы в типажах; это единственное место, где вы можете писать fn но не async fn . Очевидно, что асинхронные методы были бы мощной абстракцией, и мы хотим их поддерживать.

Асинхронный метод функционально будет рассматриваться как метод, возвращающий связанный тип, который будет реализовывать future; каждый асинхронный метод будет генерировать уникальный будущий тип для конечного автомата, в который этот метод преобразуется.

Однако, поскольку это будущее будет захватывать все входные данные, любые параметры времени или типа входа также должны быть захвачены в этом состоянии. Это эквивалентно концепции, называемой универсальными связанными типами , функции, которую мы давно хотели, но еще не реализовали должным образом. Таким образом, разрешение асинхронных методов связано с разрешением общих связанных типов.

Есть также нерешенные проблемы с дизайном. Например, взаимозаменяемы ли асинхронные методы с методами, возвращающими будущие типы, которые будут иметь такую ​​же сигнатуру? Кроме того, асинхронные методы представляют дополнительные проблемы, связанные с автоматическими характеристиками, поскольку вам может потребоваться, чтобы будущее, возвращаемое каким-либо асинхронным методом, реализовывало автоматическую характеристику, когда вы абстрагируете по характеристике с помощью асинхронного метода.

Как только мы получим хотя бы эту минимальную поддержку, появятся другие соображения по проектированию будущих расширений, например, возможность сделать асинхронные методы «объектно-безопасными».

Генераторы и асинхронные генераторы

У нас есть функция нестабильного генератора, использующая то же преобразование конечного автомата сопрограммы, чтобы принимать функции, которые дают несколько значений, и превращать их в конечный автомат. Наиболее очевидным вариантом использования этой функции является создание функций, которые компилируются в «итераторы», точно так же, как асинхронные функции компилируются в
фьючерсы. Точно так же мы могли бы скомпоновать эти две функции для создания асинхронных генераторов - функций, которые компилируются в «потоки», асинхронный эквивалент итераторов. Для этого есть действительно ясные варианты использования в сетевом программировании, которое часто включает потоки сообщений, отправляемых между системами.

У генераторов есть много открытых вопросов по дизайну, потому что это очень гибкая функция с множеством возможных вариантов. Окончательный дизайн генераторов в Rust с точки зрения синтаксиса и библиотечных API все еще находится в стадии неопределенности.

A-async-await AsyncAwait-Focus F-async_await I-nominated T-lang disposition-merge finished-final-comment-period

Самый полезный комментарий

Последний период комментариев с предложением о слиянии в соответствии с приведенным выше обзором завершен .

Как автоматизированный представитель процесса управления, я хотел бы поблагодарить автора за их работу и всех, кто внес свой вклад.

RFC скоро будет объединен.

Все 58 Комментарий

@rfcbot fcp слияние

Член команды @withoutboats предложил объединить это. Следующим шагом является проверка остальными членами команды, отмеченными тегами:

  • [x] @Centril
  • [x] @cramertj
  • [x] @eddyb
  • [x] @joshtriplett
  • [x] @nikomatsakis
  • [] @pnkfelix
  • [x] @scottmcm
  • [x] @withoutboats

Обеспокоенность:

После того, как большинство рецензентов одобрит (и не более двух утверждений остаются нерешенными), наступит последний период для комментариев. Если вы заметили серьезную проблему, которая не поднималась ни на одном этапе этого процесса, сообщите об этом!

См. Этот документ для получения информации о том, какие команды могут дать мне члены команды, отмеченные тегами.

(Просто зарегистрируйте существующие блокираторы в отчете выше, чтобы убедиться, что они не соскользнут)

@rfcbot касается реализации-работы-блокировки-стабилизации

Член команды ... предложил объединить это

Как можно объединить проблему Github (а не пулреквест)?

@vi Бот немного глуповат и не проверяет, проблема это или PR :) Здесь вы можете заменить «объединить» на «принять».

Вау, спасибо за исчерпывающее резюме! Я слежу лишь косвенно, но полностью уверен, что вы в курсе всего.

@rfcbot просмотрел

Можно ли явно добавить «Проблемы Triage AsyncAwait-Unclear» к блокираторам стабилизации (и / или зарегистрировать беспокойство по этому поводу)?

У меня есть https://github.com/rust-lang/rust/issues/60414, который я считаю важным (очевидно, это моя ошибка: p), и я хотел бы, по крайней мере, явно отложить его до стабилизации :)

Я просто хотел бы выразить благодарность сообществу за усилия, которые команды Rust вложили в эту функцию! Было много разработок, обсуждений и несколько сбоев в общении, но, по крайней мере, я и, надеюсь, многие другие уверены, что благодаря всему этому мы нашли лучшее решение для Rust. : тада:

(Тем не менее, я хотел бы видеть упоминание о проблемах с мостом к API-интерфейсам системы на основе завершения и асинхронной отмены в будущих возможностях. TL; DR им по-прежнему приходится передавать собственные буферы. Это проблема библиотеки, но одна с упоминанием.)

Я также хотел бы увидеть упоминание о проблемах с API на основе завершения. (см. этот внутренний поток для контекста). Принимая во внимание IOCP и введение io_uring , который может стать Пути для асинхронного ввода-вывода в Linux, я думаю, что важно иметь четкий путь для их обработки. Идеи гипотетического асинхронного отбрасывания IIUC не могут быть реализованы безопасно, а передача собственных буферов будет менее удобной и потенциально менее производительной (например, из-за худшего местоположения или из-за дополнительных копий).

@newpavlov Я реализовал аналогичные вещи для Fuchsia, и это вполне возможно обойтись без async drop. Для этого есть несколько различных способов, например использование пула ресурсов, при котором для получения ресурса потенциально необходимо дождаться завершения некоторых работ по очистке старых ресурсов. Текущий Futures API может использоваться и использовался для эффективного решения этих проблем в производственных системах.

Однако эта проблема касается стабилизации async / await, которая ортогональна дизайну Futures API, который уже стабилизировался. Не стесняйтесь задавать дополнительные вопросы или открывать вопрос для обсуждения по репо futures-rs.

@Ekleog

Можно ли явно добавить «Проблемы Triage AsyncAwait-Unclear» к блокираторам стабилизации (и / или зарегистрировать беспокойство по этому поводу)?

Ага, мы этим занимаемся каждую неделю. Запишите эту конкретную проблему (# 60414), я считаю ее важной и хотел бы, чтобы она была исправлена, но мы еще не смогли решить, должна ли она блокировать стабилизацию, тем более что это уже наблюдается в -> impl Trait functions.

@cramertj Спасибо! Я думаю, что проблема # 60414 в основном заключается в том, что «ошибка может возникнуть очень быстро сейчас», в то время как с -> impl Trait похоже, что никто даже не заметил ее раньше - тогда ничего страшного, если она все равно будет отложена, некоторые проблемы придется :) (FWIW это возникло в естественном коде в функции, где я возвращаю как () в одном месте, и T::Assoc в другом месте, из-за чего IIRC не смог заставить его скомпилировать - не проверял код с момента открытия # 60414, так что, возможно, я ошибаюсь)

@Ekleog Да, в этом есть смысл! Я определенно понимаю, почему это было бы больно - я создал поток zulip, чтобы больше погрузиться в эту конкретную проблему.

РЕДАКТИРОВАТЬ: неважно, я пропустил цель 1.38 .

@cramertj

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

Разве они не менее эффективны по сравнению с хранением буферов как части будущего состояния? Меня больше всего беспокоит то, что текущий дизайн не будет иметь нулевых затрат (в том смысле, что вы сможете создать более эффективный код, отказавшись async абстракции

@герцог

Команда lang, конечно, может судить об этом лучше меня, но откладывание до 1.38 для обеспечения стабильной реализации казалось бы гораздо более разумным.

Эта проблема нацелена на 1.38, см. Первую строку описания.

@huxi спасибо, я это пропустил. Отредактировал свой комментарий.

@newpavlov

Разве они не менее эффективны по сравнению с хранением буферов как части будущего состояния? Меня больше всего беспокоит то, что текущий дизайн не будет иметь нулевую стоимость (в том смысле, что вы сможете создать более эффективный код, отказавшись от асинхронной абстракции) и менее эргономичным для API на основе завершения, и нет четкого способа исправить Это. Это ни в коем случае не шумиха, но я думаю, что важно не забывать о подобных недостатках в дизайне, поэтому просьба упомянуть об этом в OP.

Нет, не обязательно, но давайте перенесем это обсуждение в отдельную тему, поскольку она не связана со стабилизацией async / await.

(Тем не менее, я хотел бы видеть упоминание о проблемах с мостом к API-интерфейсам системы на основе завершения и асинхронной отмены в будущих возможностях. TL; DR им по-прежнему приходится передавать собственные буферы. Это проблема библиотеки, но одна с упоминанием.)

Я также хотел бы увидеть упоминание о проблемах с API на основе завершения. (см. этот внутренний поток для контекста). Учитывая IOCP и введение io_uring, которое может стать путем для асинхронного ввода-вывода в Linux, я думаю, что важно иметь четкий путь для их обработки.

Я согласен с Тейлором в том, что обсуждение проектов API в этом проблемном пространстве было бы не по теме, но я хочу затронуть один конкретный аспект этих комментариев (и это обсуждение io_uring в целом), который имеет отношение к стабилизации async / await: проблема сроки.

io_uring - это интерфейс, который появится в Linux в этом , 2019 году. Проект Rust работает над абстракцией фьючерсов с 2015 года, четыре года назад. Фундаментальный выбор в пользу опроса, основанного на API, основанного на завершении, произошел в 2015 и 2016 годах. На RustCamp в 2015 году Карл Лерш рассказал о том, почему он сделал этот выбор в mio, базовой абстракции ввода-вывода. В этом сообщении в блоге в 2016 году Аарон Турон рассказал о преимуществах создания абстракций более высокого уровня. Эти решения были приняты очень давно, и без них мы не смогли бы дойти до того, что мы сейчас находимся.

Предложения о том, что нам следует пересмотреть нашу базовую модель будущего, - это предложения о том, что нам следует вернуться к состоянию, в котором мы были 3 или 4 года назад, и начать с этого момента. Какого рода абстракция могла бы охватывать модель ввода-вывода на основе завершения без введения накладных расходов для примитивов более высокого уровня, как описал Аарон? Как мы сопоставим эту модель с синтаксисом, который позволяет пользователям писать «обычные аннотации Rust + второстепенные», как это делает async / await? Как мы сможем справиться с интеграцией этого в нашу модель памяти, как мы это сделали для этих конечных автоматов с помощью вывода? Попытка дать ответы на эти вопросы была бы не по теме этой темы; Дело в том, что ответить на них и доказать правильность ответов - это работа. То, что до сих пор составляет солидное десятилетие трудовых лет между разными участниками, придется переделывать снова.

Цель Rust - выпустить продукт, который люди смогут использовать, а это значит, что мы должны его выпустить . Мы не всегда можем перестать смотреть в будущее на то, что может стать большим событием в следующем году, и перезапускать наш процесс проектирования, чтобы учесть это. Мы делаем все, что в наших силах, исходя из ситуации, в которой мы оказались. Очевидно, может быть неприятно чувствовать, что мы едва пропустили что-то важное, но в настоящее время у нас нет полного представления: а) о том, какой лучший результат для обработки io_uring, б) насколько важным будет io_uring для экосистемы в целом. На основании этого мы не можем отменить 4 года работы.

Подобные, возможно, даже более серьезные ограничения Rust уже существуют в других областях. Хочу выделить одну, на которую я смотрел с Ником Фицджеральдом прошлой осенью: интеграция с wasm GC. План обработки управляемых объектов в wasm состоит в том, чтобы по существу сегментировать пространство памяти, чтобы они существовали в отдельном адресном пространстве от неуправляемых объектов (действительно, когда-нибудь во многих отдельных адресных пространствах). Модель памяти Rust просто не предназначена для обработки отдельных адресных пространств, и любой небезопасный код, который сегодня имеет дело с кучей памяти, предполагает, что существует только одно адресное пространство. Хотя мы набросали как критические, так и технически неразрывные, но чрезвычайно разрушительные технические решения, наиболее вероятный путь вперед - это признать, что наша история с wasm GC может быть не совсем оптимальной , потому что мы имеем дело с ограничениями Rust как это существует.

Интересный аспект, который мы здесь стабилизируем, заключается в том, что мы делаем самодостаточные структуры доступными из безопасного кода. Что делает это интересным, так это то, что в Pin<&mut SelfReferentialGenerator> у нас есть изменяемая ссылка (хранящаяся как поле в Pin ), указывающая на все состояние генератора, и у нас есть указатель внутри этого состояния, указывающий в другой кусок государства. Этот внутренний указатель является псевдонимом изменяемой ссылки!

Насколько мне известно, изменяемая ссылка не используется для фактического доступа к той части памяти, на которую указывает указатель на другое поле. (В частности, не существует метода clone или такого, который мог бы читать поле указателя с использованием любого другого указателя, кроме самореферентного.) Тем не менее, это становится все ближе к наличию изменяемого псевдонима ссылки с нечто большее, чем что-либо еще в основной экосистеме, в частности все, что поставляется с самим rustc. «Линия», по которой мы здесь движемся, становится очень тонкой, и мы должны быть осторожны, чтобы не потерять все эти прекрасные оптимизации, которые мы хотим сделать на основе изменяемых ссылок.

Вероятно, на данный момент мы мало что можем с этим поделать, в частности, потому что Pin уже стабилен, но я считаю, что стоит отметить, что это значительно усложнит все правила, для которых разрешено использование псевдонимов и который не. Если вы думали, что Stacked Borrows - это сложно, приготовьтесь к тому, что ситуация ухудшится.

Копия https://github.com/rust-lang/unsafe-code-guidelines/issues/148

Насколько мне известно, изменяемая ссылка не используется для фактического доступа к той части памяти, на которую указывает указатель на другое поле.

Люди говорили о том, чтобы все эти типы сопрограмм реализовывали Debug , похоже, что этот разговор также должен включать рекомендации по небезопасному коду, чтобы быть уверенным, что безопасно отлаживать печать.

Люди говорили о том, чтобы все эти типы сопрограмм реализовывали отладку, похоже, что этот разговор также должен включать рекомендации по небезопасному коду, чтобы быть уверенным, что безопасно отлаживать печать.

Действительно. Такая реализация Debug , если она печатает поля с самими ссылками, скорее всего, запретит оптимизацию на основе ссылок на уровне MIR внутри генераторов.

Обновление по блокираторам:

Оба блокиратора высокого уровня достигли большого прогресса и, возможно, оба уже закончили (?). Было бы здорово получить дополнительную информацию от @cramertj @tmandry и @nikomatsakis :

  • Проблема с несколькими сроками жизни должна была быть исправлена ​​с помощью # 61775.
  • Вопрос размера более неоднозначен; всегда будет больше оптимизаций, но я думаю, что низко висящий плод отказа от очевидного экспоненциального увеличения ножных ружей в основном решен?

Это оставляет документацию и тестирование как основные препятствия для стабилизации этой функции. @Centril неоднократно выражал озабоченность по поводу того, что эта функция недостаточно хорошо протестирована или отполирована; @Centril есть ли где-нибудь, где вы перечислили конкретные проблемы, которые можно было бы

Не уверен, водит ли кто документацию. Любой, кто хочет сосредоточиться на улучшении древовидной документации в книге, справочнике и т. Д., Окажет большую услугу! Документация вне дерева, например, в репозитории Futures или areweasyncyet, имеет немного дополнительного времени.

На сегодняшний день у нас есть 6 недель до закрытия бета-версии, поэтому предположим, что у нас есть 4 недели (до 1 августа), чтобы сделать все это, чтобы быть уверенными, что мы не упустим 1.38.

Вопрос размера более неоднозначен; всегда будет больше оптимизаций, но я думаю, что низко висящий плод отказа от очевидного экспоненциального увеличения ножных ружей в основном решен?

Думаю, да, и некоторые другие недавно закрылись; но есть и другие проблемы с блокировкой .

@Centril есть ли где-нибудь, где вы перечислили конкретные проблемы, которые можно было бы

Там есть папка со списком вещей, которые мы хотели протестировать, и https://github.com/rust-lang/rust/issues/62121. Помимо этого, я постараюсь как можно скорее пересмотреть области, которые, по моему мнению, недостаточно протестированы. Тем не менее, некоторые области сейчас довольно хорошо протестированы.

Любой, кто хочет сосредоточиться на улучшении древовидной документации в книге, справочнике и т. Д., Окажет большую услугу!

Действительно; Буду рад пересмотреть PR к ссылке. Также cc @ehuss.


Я также хотел бы переместить async unsafe fn из MVP в его собственные ворота функций, потому что я думаю: а) он мало используется, б) он не особенно хорошо протестирован, в) он якобы ведет себя странно, потому что .await point - это не то место, где вы пишете unsafe { ... } и это понятно из "дырявой реализации POV", но не столько из POV эффектов, d) он мало обсуждался и не был включен в RFC ни этот отчет, и д) мы сделали это с помощью const fn и он работал нормально. (Я могу написать PR функции ворот)

Я нормально отношусь к дестабилизации async unsafe fn , хотя я скептически отношусь к тому, что мы закончим с другим дизайном, чем нынешний. Но кажется разумным дать нам время разобраться в этом!

Я создал https://github.com/rust-lang/rust/issues/62500 для перемещения async unsafe fn в отдельный элемент доступа и внес его в список блокировщиков. Думаю, нам, вероятно, также следует создать проблему с правильным отслеживанием.

Я очень скептически отношусь к тому, что мы достигнем другого дизайна для async unsafe fn и удивлен решением не включать его в начальный раунд стабилизации. Я написал несколько async fn s, которые небезопасны и, я полагаю, сделают их async fn really_this_function_is_unsafe() или что-то в этом роде. Это похоже на регресс базовых ожиданий пользователей Rust в плане возможности определять функции, для вызова которых требуется unsafe { ... } . Еще одно окно функций будет способствовать созданию впечатления, что async / await незавершено.

@cramertj, кажется, мы должны обсудить! Я создал для него тему Zulip , чтобы не допустить чрезмерной перегрузки этой проблемы с отслеживанием.

Что касается будущих размеров, оптимизированы случаи, влияющие на каждую точку await . Последняя оставшаяся проблема, о которой я знаю, - это # ​​59087, где любое заимствование будущего до ожидания может удвоить размер, выделенный для этого будущего. Это довольно прискорбно, но все же немного лучше, чем мы были раньше.

У меня есть представление о том, как исправить эту проблему, но если это не встречается чаще, чем я представляю, вероятно, это не должно быть препятствием для стабильного MVP.

Тем не менее, мне все еще нужно посмотреть на влияние этих оптимизаций на Fuchsia (это было заблокировано некоторое время, но должно исчезнуть сегодня или завтра). Вполне возможно, что мы обнаружим больше случаев, и нам нужно будет решить, следует ли какой-либо из них блокировать.

@cramertj (Напоминание: я использую async / await и хочу, чтобы он стабилизировался как можно скорее) Ваш аргумент звучит как аргумент в пользу задержки стабилизации async / await, а не для стабилизации async unsafe прямо сейчас без надлежащих экспериментов и размышлений.

Тем более, что он не был включен в RFC, и потенциально может вызвать еще один дерьмовый шторм «имплицитный признак в позиции аргумента», если он будет вытеснен таким образом.

[Боковое примечание, которое не заслуживает здесь обсуждения: «Еще одна функция создает впечатление, что async / await не завершена», я обнаруживал ошибку каждые несколько часов использования async / await, распространяемую немногими месяцы, законно необходимые команде rustc для их исправления, и это то, что заставляет меня говорить, что это незавершенное. Последняя проблема была исправлена ​​несколько дней назад, и я очень надеюсь, что не обнаружу еще одну, когда снова попытаюсь скомпилировать свой код с помощью более новой версии rustc, но ...]

Ваш аргумент звучит как аргумент в пользу отсрочки стабилизации async / await, а не для стабилизации небезопасного асинхронного режима прямо сейчас без надлежащих экспериментов и размышлений.

Нет, это не аргумент в пользу этого. Я считаю, что async unsafe готов, и не могу представить для него другой дизайн. Я считаю, что если его не включить в этот первоначальный выпуск, это приведет только к негативным последствиям. Я не верю, что отсрочка async / await в целом или async unsafe частности не даст лучшего результата.

не могу представить для этого другого дизайна

Альтернативный дизайн, хотя и тот, который определенно требует сложных расширений: async unsafe fn - это unsafe для .await , а не call() . Причина этого в том, что _не может быть сделано ничего опасного_ в точке, где вызывается async fn и создает impl Future . Все, что делает этот шаг, - это помещает данные в структуру (фактически, все async fn - это const для вызова). Фактическая точка опасности - продвигаться в будущее с помощью poll .

(imho, если unsafe происходит немедленно, unsafe async fn имеет больше смысла, а если unsafe задерживается, async unsafe fn имеет больше смысла.)

Конечно, если мы никогда не сможем сказать, например, unsafe Future где все методы Future небезопасны для вызова, тогда «поднятие» unsafe на создание impl Future , и контракт этого unsafe заключается в безопасном использовании полученного будущего. Но это также можно почти тривиально сделать без unsafe async fn , просто «обессахаривая» вручную в блок async : unsafe fn os_stuff() -> impl Future { async { .. } } .

Вдобавок к этому, однако, возникает вопрос, существует ли на самом деле способ иметь инварианты, которые необходимо сохранять после запуска poll ing, которые не нужно сохранять при создании. В Rust часто используется конструктор unsafe для безопасного типа (например, Vec::from_raw_parts ). Но главное здесь то, что после построения тип _ нельзя_ неправильно использовать; область действия unsafe завершена. Такая оценка небезопасности является ключом к гарантиям Rust. Если вы введете unsafe async fn который упаковывает безопасный impl Future с требованиями к тому, как и когда он опрашивается, а затем передаете его в безопасный код, этот безопасный код внезапно оказывается внутри вашего небезопасного барьера. И это _ очень_ вероятно произойдет, как только вы воспользуетесь этим будущим любым способом, кроме немедленного его ожидания, поскольку оно, скорее всего, будет проходить через _ некоторый_ внешний комбинатор.

Я предполагаю, что TL; DR этого заключается в том, что определенно есть углы async unsafe fn которые следует обсудить должным образом, прежде чем стабилизировать его, особенно с потенциальным введением направления const Trait (у меня есть черновик блога сообщение об обобщении этого на "систему слабых" эффектов "с любым ключевым словом fn ). Тем не менее, unsafe async fn может быть достаточно ясным относительно «порядка» / «позиционирования» unsafe для стабилизации.

Я считаю, что основанная на эффектах черта unsafe Future не только недоступна для всего, что мы знаем, как выразить на языке или компиляторе сегодня, но что в конечном итоге это будет худший дизайн из-за дополнительного эффекта - полиморфизм, который потребуются комбинаторам.

Ничего небезопасного сделать нельзя в точке, где вызывается async fn и создает имплицитное будущее. Все, что делает этот шаг, - это помещает данные в структуру (фактически, все async fn вызываются как const). Фактическая точка небезопасности - опросы в будущее.

Это правда, что, поскольку async fn не может запускать какой-либо пользовательский код до .await ed, любое неопределенное поведение, вероятно, будет отложено до вызова .await . Я думаю, однако, что есть важное различие между точкой UB и точкой unsafe ty. Фактическая точка unsafe ty - это то место, где автор API решает, что пользователь должен пообещать, что выполняется набор нестатически проверяемых инвариантов, даже если результат нарушения этих инвариантов не приведет к UB пока позже в другом безопасном коде. Одним из распространенных примеров этого является функция unsafe для создания значения, реализующего признак с помощью безопасных методов (именно то, что это такое). Я видел, как это использовалось, чтобы гарантировать, что, например, типы, реализующие Visitor -trait, реализации которых полагаются на инварианты unsafe могут быть надежно использованы, требуя unsafe для создания типа. Другие примеры включают такие вещи, как slice::from_raw_parts , которые сами по себе не вызывают UB (не считая инвариантов валидности типа), но доступ к результирующему срезу будет.

Я не верю, что async unsafe fn представляет собой уникальный или интересный случай здесь - он следует хорошо зарекомендовавшему себя шаблону для выполнения поведения unsafe за безопасным интерфейсом, требуя unsafe конструктор.

@cramertj Тот факт, что вам даже

Напомним, цитата из ридми:

Вам необходимо выполнить этот процесс, если [...]:

  • Любое семантическое или синтаксическое изменение языка, не являющееся исправлением ошибки.
  • [... а также нецитированные материалы]

Я не говорю, что в текущий дизайн произойдут какие-либо изменения. На самом деле, подумав об этом несколько минут, я подумал, что это, вероятно, лучший дизайн, который я мог придумать. Но процесс - это то, что позволяет нам избежать того, чтобы наши убеждения стали опасными для Rust, и нам не хватает мудрости многих людей, которые следят за репозиторием RFC, но не читают каждую проблему, не следуя процессу здесь.

Иногда имеет смысл не следовать процессу. Здесь я не вижу срочности, которая могла бы служить основанием для игнорирования процесса только во избежание двухнедельной задержки FCP.

Поэтому, пожалуйста, позвольте rust быть честным со своим сообществом в отношении обещаний, которые он дает в собственном файле readme, и просто держите эту функцию ниже ворот функций, пока не будет хотя бы принятый RFC и, надеюсь, еще какое-то его использование в дикой природе. Будь то шлюз функции async / await целиком или просто шлюз небезопасно-асинхронной функции, мне все равно, но просто не стабилизируйте то, что (AFAIK) мало использовалось за пределами async-wg и почти не известно в общее сообщество.

Я пишу первый проход по справочному материалу для книги. Попутно я заметил, что в RFC async-await сказано, что поведение оператора ? еще не определено. И все же, похоже, он отлично работает в асинхронном блоке ( игровая площадка ). Должны ли мы переместить это в отдельное окно функций? Или это было решено в какой-то момент? Я не видел этого в отчете о стабилизации, но, возможно, пропустил.

(Я также задавал этот вопрос в Zulip и предпочел бы ответы там, так как мне легче управлять.)

Да, это обсуждалось и решалось вместе с поведением return , break , continue et. al. которые все делают «единственно возможное» и ведут себя так, как если бы они были внутри замыкания.

let f = unsafe { || {...} }; также безопасно вызывать, а IIRC это эквивалентно перемещению unsafe внутрь закрытия.
То же самое для unsafe fn foo() -> impl Fn() { || {...} } .

Для меня это достаточно прецедент, когда «небезопасная вещь происходит после выхода из области unsafe ».

То же самое и в других местах. Как указывалось ранее, unsafe не всегда находится там, где мог бы быть потенциальный UB. Пример:

    let mut vec: Vec<u32> = Vec::new();

    unsafe { vec.set_len(100); }      // <- unsafe

    let val = vec.get(5).unwrap();     // <- UB
    println!("{}", val);

Мне это просто кажется неправильным пониманием небезопасности - unsafe не означает, что «здесь происходит небезопасная операция» - это означает «Я гарантирую, что соблюдаю необходимые инварианты здесь». Хотя вы можете поддерживать инварианты в точке ожидания, поскольку он не включает в себя переменных параметров, это не очень очевидный сайт для проверки того, что вы поддерживаете инварианты. Это имеет гораздо больше смысла и гораздо больше соответствует тому, как работают все наши небезопасные абстракции, чтобы гарантировать соблюдение инвариантов на сайте вызова.

Это связано с тем, почему представление о небезопасности как об эффекте приводит к неточным интуициям (как утверждал Ральф, когда эта идея впервые была высказана в прошлом году). Небезопасность специально, преднамеренно, не заразна. Хотя вы можете писать небезопасные функции, которые вызывают другие небезопасные функции и просто перенаправляют их инварианты вверх по стеку вызовов, это не обычный способ использования unsafe, и на самом деле это синтаксический маркер, используемый для определения контрактов на значения и ручной проверки этого вы их поддерживаете.

Так что дело не в том, что для каждого проектного решения нужен целый RFC, но мы работали над тем, чтобы обеспечить большую ясность и структуру того, как принимаются решения. Список основных моментов для принятия решения в открытии этого выпуска является тому примером. Используя доступные нам инструменты, я хотел бы попытаться достичь структурированного консенсуса по этой проблеме небезопасных асинхронных fns, так что это сводный пост с опросом.

async unsafe fn

async unsafe fns - это асинхронные функции, которые можно вызывать только внутри небезопасного блока. Внутри их тело считается небезопасным прицелом. Основной альтернативой было бы сделать async unsafe fns небезопасным для ожидания , а не для вызова. Есть ряд веских причин предпочесть тот дизайн, в котором называть их небезопасно:

  1. Синтаксически это согласуется с поведением неасинхронных небезопасных функций, которые также небезопасно вызывать.
  2. Это больше соответствует тому, как небезопасно работает в целом. Небезопасная функция - это абстракция, которая зависит от некоторых инвариантов, поддерживаемых ее вызывающей стороной. То есть дело не в том, что речь идет о маркировке «где происходит небезопасная операция», а в том, «где инвариант гарантированно соблюдается». Гораздо разумнее проверять, поддерживаются ли инварианты на сайте вызова, где аргументы фактически указаны, чем на сайте ожидания, отдельно от того, когда аргументы были выбраны и проверены. Это очень нормально для небезопасных функций в целом, которые часто определяют какое-то состояние, которое другие безопасные функции ожидают быть правильными.
  3. Это больше согласуется с понятием десугарирования сигнатур async fn, где вы можете смоделировать сигнатуру как эквивалент удаления модификатора async и упаковки возвращаемого типа в будущем.
  4. Альтернативу невозможно реализовать в ближайшей или среднесрочной перспективе (имеется в виду несколько лет). Невозможно создать будущее, которое было бы небезопасно опрашивать на языке Rust, разработанном в настоящее время. Какое-то «небезопасное как эффект» было бы огромным изменением, которое имело бы далеко идущие последствия и потребовало бы рассмотрения того, как оно обратно совместимо с небезопасным, как оно уже существует сегодня (например, обычные небезопасные функции и блоки). Добавление async unsafe fns существенно не меняет ситуацию, тогда как async unsafe fns в текущей интерпретации unsafe имеет реальные практические варианты использования в краткосрочной и среднесрочной перспективе.

@rfcbot ask lang "

Понятия не имею, как провести опрос с помощью rfcbot, но, по крайней мере, я его номинировал.

Член команды @withoutboats обратился к командам: T-lang за консенсусом по:

«Принимаем ли мы стабилизацию async unsafe fn как async fn, вызывать которую небезопасно?»

  • [x] @Centril
  • [x] @cramertj
  • [x] @eddyb
  • [] @joshtriplett
  • [x] @nikomatsakis
  • [] @pnkfelix
  • [] @scottmcm
  • [x] @withoutboats

@withoutboats

Я хотел бы нанести удар по структурированному консенсусу по этой проблеме небезопасных async fns, так что это сводный пост с опросом.

Спасибо, что написали. Обсуждение убедило меня, что async unsafe fn как он работает сегодня ночью, ведет себя правильно. (Вероятно, следует добавить некоторые тесты, поскольку он выглядел скудным.) Кроме того, не могли бы вы внести в отчет вверху части отчета + описание того, как ведет себя async unsafe fn ?

Это больше соответствует тому, как небезопасно работает в целом. Небезопасная функция - это абстракция, которая зависит от некоторых инвариантов, поддерживаемых ее вызывающей стороной. То есть дело не в том, что речь идет о маркировке «где происходит небезопасная операция», а в том, «где инвариант гарантированно соблюдается». Гораздо разумнее проверять, поддерживаются ли инварианты на сайте вызова, где аргументы фактически указаны, чем на сайте ожидания, отдельно от того, когда аргументы были выбраны и проверены. Это очень нормально для небезопасных функций в целом, которые часто определяют какое-то состояние, которое другие безопасные функции ожидают быть правильными.

Как человек, не обращающий слишком пристального внимания, я согласен и думаю, что решение здесь - хорошая документация.

Я мог бы ошибиться здесь, но с учетом этого

  • Futures комбинаторны по своей природе, принципиально то, что они могут быть составлены.
  • Точки ожидания внутри будущей реализации обычно являются невидимой деталью реализации.
  • будущее очень далеко от контекста выполнения, и реальный пользователь может находиться между ними, а не в корне.

Мне кажется, что инварианты, зависящие от конкретного ожидающего использования / поведения, находятся где-то между плохой идеей и невозможностью безопасного правила.

Если есть случаи, когда ожидаемое выходное значение - это то, что участвует в поддержании инвариантов, я предполагаю, что в будущем может просто быть выход, который является оболочкой, требующей небезопасного доступа, например

struct UnsafeOutput<T>(T);
impl<T> UnsafeOutput<T> {
    unsafe fn unwrap(self) -> T { self.0 }
}

Учитывая, что значение unsafe предшествует значению async в этом "раннем небезопасном состоянии", я был бы гораздо более счастлив, если бы порядок модификаторов был unsafe async fn чем async unsafe fn , потому что unsafe (async fn) гораздо более явно отображает это поведение, чем async (unsafe fn) .

Я с радостью соглашусь, но я твердо уверен, что в представленном здесь порядке упаковки есть unsafe снаружи, и порядок модификаторов может помочь прояснить это. ( unsafe - модификатор async fn , а не async модификатор unsafe fn .)

Я с радостью приму и то, и другое, но я твердо уверен, что в представленном здесь порядке упаковки есть unsafe снаружи, и порядок модификаторов может помочь прояснить это. ( unsafe - модификатор async fn , а не async модификатор unsafe fn .)

Я был с вами до вашего последнего пункта в скобках. Запись @withoutboats дает мне довольно ясно понять, что, если небезопасность рассматривается на сайте вызова, на самом деле у вас есть unsafe fn (который вызывается в асинхронном контексте).

Я бы сказал, мы покрасим велосипедную навесу async unsafe fn .

Я думаю, что async unsafe fn имеет больше смысла, но я также считаю, что мы должны грамматически принимать любой порядок между async, unsafe и const. Но для меня async unsafe fn имеет больше смысла, поскольку вы удаляете асинхронный код и изменяете возвращаемый тип, чтобы "десахарировать" его.

Альтернативу невозможно реализовать в ближайшей или среднесрочной перспективе (имеется в виду несколько лет). Невозможно создать будущее, которое было бы небезопасно опрашивать на языке Rust, разработанном в настоящее время.

FWIW Я столкнулся с аналогичной проблемой, о которой упоминал в RFC2585, когда дело доходит до замыканий внутри unsafe fn и свойств функции. Я не ожидал, что unsafe async fn вернет Future с безопасным методом poll , а вместо этого вернет UnsafeFuture с unsafe метод опроса. (*) Тогда мы могли бы заставить .await также работать с UnsafeFuture s, когда он используется внутри блоков unsafe { } , но не иначе.

Эти две будущие черты будут огромным изменением по сравнению с тем, что у нас есть сегодня, и они, вероятно, внесут много проблем с возможностью компоновки. Значит, корабль для исследования альтернатив, вероятно, ушел. В частности, поскольку это будет отличаться от того, как сегодня работают черты Fn (например, у нас нет черты UnsafeFn или аналогичной, и моя проблема в RFC2585 заключалась в том, что создание замыкания внутри unsafe fn возвращает закрытие, которое impls Fn() , то есть безопасное для вызова, даже если это закрытие может вызывать небезопасные функции.

Создание «небезопасного» будущего или закрытие не является проблемой, проблема состоит в том, чтобы вызвать их, не доказывая, что это безопасно, особенно когда их типы не говорят, что это должно быть сделано.

(*) Мы можем предоставить общий набор UnsafeFuture для всех Future s, а также можем предоставить UnsafeFuture unsafe метод Future который безопасен для poll .

Вот мои два цента:

  • Объяснение @cramertj (https://github.com/rust-lang/rust/issues/62149#issuecomment-510166207) убеждает меня, что unsafe async-функции - это правильный дизайн.
  • Я предпочитаю фиксированный порядок ключевых слов unsafe и async
  • Я немного предпочитаю порядок unsafe async fn потому что порядок кажется более логичным. Подобно «быстрому электромобилю» против «быстрому электрическому автомобилю». В основном потому, что async fn обессахаривает fn . Итак, логично, что два ключевых слова находятся рядом друг с другом.

Я думаю, что let f = unsafe { || { ... } } должно сделать f безопасным, признак UnsafeFn никогда не должен вводиться, и априори .await ing и async unsafe fn должны быть безопасно. Любой UnsafeFuture требует веского обоснования!

Все это следует потому, что unsafe должно быть явным, а Rust должен подтолкнуть вас обратно в безопасную страну. Также по этому токену f ... должно _не_ быть небезопасным блоком, следует принять https://github.com/rust-lang/rfcs/pull/2585 и async unsafe fn должно быть безопасное тело.

Я думаю, что последний пункт может оказаться весьма важным. Возможно, что в каждом async unsafe fn будет использоваться блок unsafe , но аналогично большинству из них будет полезен некоторый анализ безопасности, и многие из них звучат достаточно сложно, чтобы легко допустить ошибку.

Мы никогда не должны обходить проверку заимствований при захвате, в частности, для закрытия.

Итак, мой комментарий здесь: https://github.com/rust-lang/rust/issues/62149#issuecomment -511116357 - очень плохая идея.

Признак UnsafeFuture потребует от вызывающего написать unsafe { } для опроса будущего, но вызывающий не знает, какие обязательства должны быть там подтверждены, например, если вы получите Box<dyn UnsafeFuture> unsafe { future.poll() } безопасно? Для всех фьючерсов? Вы не можете знать. Так что это было бы совершенно бесполезно, как указал @rpjohnst в разногласиях для аналогичной черты UnsafeFn .

Требовать, чтобы будущее всегда было безопасным для опроса, имеет смысл, и процесс построения будущего, которое должно быть безопасным для опроса, может быть небезопасным; Полагаю, вот что такое async unsafe fn . Но в этом случае элемент fn может задокументировать, что необходимо поддержать, чтобы возвращенное будущее можно было безопасно опросить.

@rfcbot реализация-работа-блокировка-стабилизация

Насколько мне известно, есть еще 2 известных блокиратора реализации (https://github.com/rust-lang/rust/issues/61949, https://github.com/rust-lang/rust/issues/62517), и это будет еще будет хорошо добавить несколько тестов. Я разрешаю свою заботу о том, чтобы rfcbot не был нашим блокировщиком по времени, и тогда мы фактически заблокируем исправления.

@rfcbot разрешить реализацию-работу-блокировку-стабилизацию

: bell: Сейчас наступает последний период для комментариев , согласно обзору выше . : звонок:

Зарегистрирован PR стабилизации в https://github.com/rust-lang/rust/pull/63209.

Последний период комментариев с предложением о слиянии в соответствии с приведенным выше обзором завершен .

Как автоматизированный представитель процесса управления, я хотел бы поблагодарить автора за их работу и всех, кто внес свой вклад.

RFC скоро будет объединен.

Интересный аспект, который мы здесь стабилизируем, заключается в том, что мы делаем самодостаточные структуры доступными из безопасного кода. Что делает это интересным, так это то, что в Pin <& mut SelfReferentialGenerator> у нас есть изменяемая ссылка (сохраненная как поле в Pin), указывающая на все состояние генератора, и у нас есть указатель внутри этого состояния, указывающий на другую часть состояния. . Этот внутренний указатель является псевдонимом изменяемой ссылки!

Как продолжение этого, @comex действительно удалось написать некоторый (безопасный) асинхронный код на Rust, который нарушает аннотации LLVM noalias том виде, в

Была ли эта страница полезной?
0 / 5 - 0 рейтинги