Идиоматический Rust
Если вы привыкли к Java или C, рассмотрите это.
^1 В большинстве случаев вы должны предпочесть ? над .unwrap(). Однако в случае блокировок возвращенный PoisonError означает панику в другом потоке, поэтому его развертывание (тем самым распространяя панику) часто является лучшей идеей.
Async-Await 101
Если вы знакомы с async/await в C# или TypeScript, вот некоторые вещи, которые следует иметь в виду:
Основные
^1 Технически асинхронно преобразует следующий код в анонимный, сгенерированный компилятором тип конечного автомата, `f()` создает экземпляр этой машины.
^2 Контейнер `Future` всегда подразумевает `Future`, в зависимости от типов, используемых внутри async.
^3 Контейнер `Future`, управляемый рабочим потоком, вызывающим `Future::poll()` через среду выполнения напрямую или родительский `.await` косвенно.
^4 Rust не поставляется с веб сервером, для этого нужен внешний крейт, например, tokio.
Поток выполнения
В каждом `x.await` контейнер `Future` передает управление подчиненному контейнеру `Future` `x`. В какой-то момент низкоуровневый контейнер `Future`, вызываемый через `.await`, может быть ещё не готов завершиться. В этом случае рабочий поток возвращается вплоть до среды выполнения, чтобы он мог управлять другим `Future`. Некоторое время спустя среда выполнения:
- может возобновить выполнение. Обычно это происходит, если только sm / Future не упал.
- может возобновить работу с предыдущим рабочим или другим рабочим потоком (зависит от времени выполнения).
Предостережение
Учитывая поток выполнения, некоторые соображения при написании кода внутри асинхронной конструкции:
^1 Здесь мы предполагаем, что `s` — это любой нелокальный, который может быть временно переведен в недопустимое состояние; TL — это локальное хранилище любого потока, и async {}, содержит код, написанный без учета специфики исполнителя.
^2 Поскольку Drop запускается в любом случае, когда Future отбрасывается, рассмотрите возможность использования drop guard, который очищает / исправляет состояние приложения, если его нужно оставить в плохом состоянии через точки .await.
Замыкания в API
Существует отношение субтрейта `Fn: FnMut: FnOnce`. Это означает замыкание, которое реализует `Fn`, также реализует `FnMut` и `FnOnce`. Аналогично замыкание, реализующее `FnMut`, также реализует `FnOnce`.
С точки зрения точки вызова это означает:
Обратите внимание, что запрос закрытия `Fn` в качестве функции является наиболее ограничительным для вызывающего объекта; но наличие закрытия `Fn` в качестве вызывающего объекта наиболее совместимо с любой функцией.
С точки зрения того, кто определяет замыкание:
Rust предпочитает получать параметр по ссылке (что приводит к наиболее «совместимым» замыканиям с точки зрения вызывающего функцию), но может быть принудительно забран параметр и средой путем копирования или перемещения с помощью синтаксиса `move || {}`.
Это дает следующие преимущества и недостатки:
Небезопасный, необоснованный, неопределённый
Небезопасность приводит к необоснованности. Необоснованность приводит к неопределённости. Неопределенный ведет к темной стороне силы.
Безопасный код
- Безопастность имеет ограниченное значение в Rust, туманно «внутренняя профилактика неопределенного поведения (UB)».
- Обоснованность означает, что язык не позволяет использовать себя, чтобы вызвать UB.
- Крушение самолета или удаление базы данных не является UB, поэтому «безопасно» с точки зрения Rust.
- Запись в `/proc/[pid]/mem` для самостоятельного изменения кода также является «безопасной», в результате чего UB не вызывается по своей сути.
Неопределенное поведение (UB)
- Как уже упоминалось, небезопасный код подразумевает особые обещания компилятору (в противном случае он не должен быть небезопасным).
- Невыполнение какого-либо обещания (промиса) заставляет компилятор производить ошибочный код, выполнение которого приводит к UB.
- После запуска неопределенного поведения может произойти что угодно. Коварно, эффекты могут быть 1) тонкими, 2) проявляться далеко от места нарушения или 3) быть видимыми только при определенных условиях.
- Казалось бы, работающая программа (включая любое количество модульных тестов) не является доказательством того, что код UB не может потерпеть неудачу из за прихоти.
- Код с UB объективно опасен, недействителен и никогда не должен существовать.
Неподходящий код
- Любой безопасный Rust код, который может (даже только теоретически) производить UB для любого пользовательского ввода, всегда является необоснованным.
- Как и небезопасный код, который может вызывать UB по собственному желанию, нарушая вышеупомянутые обещания.
- Необоснованный код представляет собой угрозу стабильности и безопасности и нарушает основные предположения, которые имеют многие пользователи Rust.
Ответственное использование небезопасного кода
- Не используйте `unsafe`, если вам это абсолютно не нужно.
- Следуйте рекомендациям Nomicon Unsafe, всегда соблюдайте все правила безопасности и никогда не ссылайтесь на UB.
- Сведите к минимуму использование `unsafe` и инкапсулируйте их в небольшие предупреждающие модули, которые легко просматривать.
- Никогда не создавайте нездоровых абстракций, если вы не можете правильно инкапсулировать `unsafe`, не делайте этого.
- Каждое небезопасное устройство должно сопровождаться текстовыми рассуждениями, описывающими его безопасность.
Гонка кода
Гонка кода — это безопасный код 3-й стороны, который компилируется, но не соответствует ожиданиям API и может мешать вашим собственным гарантиям (безопасности).
^1 Примечательно, что при переименовании переменной из `_x` в `_ваше` также измените поведение Drop, поскольку вы измените семантику. Переменная с именем `_x` будет иметь `Drop::drop()`, выполненную в конце ее области, переменная с именем `_` может выполнить ее немедленно при «очевидном» назначении («очевидно», потому что привязка с именем `_` означает, что подстановочный знак REF отбрасывает это, это произойдет как можно скорее, часто сразу)!
Последствия
- Общий код не может быть безопасным, если безопасность зависит от сотрудничества типов w.r.t. большинство (`std::`) трейтов.
- Если требуется типовая кооперация, вы должны использовать небезопасные трейты (вероятно, реализовать свои собственные).
- Необходимо учитывать случайное выполнение кода в неожиданных местах (например, переназначение, конец области).
- Вы все еще можете наблюдать после паники.
Как следствие, безопасный, но падающи код (например, `airplane_speed<T>()`), вероятно, также должен следовать этому руководству.
Стабильность API
При обновлении API эти изменения могут нарушить работу клиентского кода. RFC Основные изменения (🔴) определенно нарушаются, в то время как незначительные изменения (🟡) могут нарушать:
Крейты:
🔴 компиляция крейта, который ранее компилировался для стабильной работы, требует ночную версию.
🟡 Изменение использования Cargo (например, добавление или удаление функций).
Модули:
🔴 Переименование/перемещение/удаление любых общедоступных элементов.
🟡 Добавление новых общедоступных элементов, так как это может нарушить код, использующий `ваш_crate::*`.
Структуры:
🔴 Добавление закрытого поля, когда все текущие поля открыты.
🔴 Добавление открытого поля, если закрытого поля не существует.
🟡 Добавление или удаление закрытых полей, если хотя бы одно из них уже существует (до и после изменения).
🟡 Переход от кортежной структуры со всеми закрытыми полями (по крайней мере, с одним полем) к нормальной структуре или наоборот.
Перечисления:
🔴 Добавление новых вариантов, может быть смягчен с помощью раннего `#[non_exhaustive]`.
🔴 Добавление новых полей в вариант.
Трейты:
🔴 Добавление элемента, не являющегося элементом по умолчанию, прерывает все существующие `impl T for S {}`.
🔴 Любое нетривиальное изменение подписей элементов затронет либо потребителей, либо исполнителей.
🟡 Добавление элемента по умолчанию, может привести к неоднозначности диспетчеризации с другими существующими трейтами.
🟡 Добавление параметра типа по умолчанию.
🔴 Реализация любого «фундаментального» трейта, так как не реализованный фундаментальный трейт уже была обещанием.
🟡 Реализация любого несущественного трейта, может также вызвать неоднозначность рассылки.
Встроенные реализации:
🟡 Добавление любых встроенных элементов, может привести к тому, что клиенты предпочтут это трейту функцию и приведут к ошибке компиляции.
Сигнатуры в определениях типов:
🔴 Ужесточение границ (например, `<T>` до `<T: Clone>`).
🟡 Ослабление границ.
🟡 Добавление параметров типа по умолчанию.
🟡 Обобщение на дженерики.
Сигнатуры в функциях:
🔴 Добавление/удаление аргументов.
🟡 Ввод нового параметра типа.
🟡 Обобщение на дженерики.
Поведенческие изменения:
🔴 / 🟡 Изменение семантики может не привести к ошибкам компилятора, но может заставить клиентов делать неправильные вещи.
Статья на list-site.