Найти в Дзене
Ржавый код

Руководство по кодированию

Оглавление

Идиоматический Rust

Если вы привыкли к Java или C, рассмотрите это.

-2

^1 В большинстве случаев вы должны предпочесть ? над .unwrap(). Однако в случае блокировок возвращенный PoisonError означает панику в другом потоке, поэтому его развертывание (тем самым распространяя панику) часто является лучшей идеей.

Async-Await 101

Если вы знакомы с async/await в C# или TypeScript, вот некоторые вещи, которые следует иметь в виду:

Основные

-3

^1 Технически асинхронно преобразует следующий код в анонимный, сгенерированный компилятором тип конечного автомата, `f()` создает экземпляр этой машины.
^2 Контейнер `Future` всегда подразумевает `Future`, в зависимости от типов, используемых внутри async.
^3 Контейнер `Future`, управляемый рабочим потоком, вызывающим `Future::poll()` через среду выполнения напрямую или родительский `.await` косвенно.
^4 Rust не поставляется с веб сервером, для этого нужен внешний крейт, например, tokio.

Поток выполнения

В каждом `x.await` контейнер `Future` передает управление подчиненному контейнеру `Future` `x`. В какой-то момент низкоуровневый контейнер `Future`, вызываемый через `.await`, может быть ещё не готов завершиться. В этом случае рабочий поток возвращается вплоть до среды выполнения, чтобы он мог управлять другим `Future`. Некоторое время спустя среда выполнения:

  • может возобновить выполнение. Обычно это происходит, если только sm / Future не упал.
  • может возобновить работу с предыдущим рабочим или другим рабочим потоком (зависит от времени выполнения).
Упрощенная диаграмма для кода, написанного внутри асинхронного блока
Упрощенная диаграмма для кода, написанного внутри асинхронного блока

Предостережение

Учитывая поток выполнения, некоторые соображения при написании кода внутри асинхронной конструкции:

-5

^1 Здесь мы предполагаем, что `s` — это любой нелокальный, который может быть временно переведен в недопустимое состояние; TL — это локальное хранилище любого потока, и async {}, содержит код, написанный без учета специфики исполнителя.
^2 Поскольку Drop запускается в любом случае, когда Future отбрасывается, рассмотрите возможность использования drop guard, который очищает / исправляет состояние приложения, если его нужно оставить в плохом состоянии через точки .await.

Замыкания в API

Существует отношение субтрейта `Fn: FnMut: FnOnce`. Это означает замыкание, которое реализует `Fn`, также реализует `FnMut` и `FnOnce`. Аналогично замыкание, реализующее `FnMut`, также реализует `FnOnce`.

С точки зрения точки вызова это означает:

-6

Обратите внимание, что запрос закрытия `Fn` в качестве функции является наиболее ограничительным для вызывающего объекта; но наличие закрытия `Fn` в качестве вызывающего объекта наиболее совместимо с любой функцией.

С точки зрения того, кто определяет замыкание:

-7

Rust предпочитает получать параметр по ссылке (что приводит к наиболее «совместимым» замыканиям с точки зрения вызывающего функцию), но может быть принудительно забран параметр и средой путем копирования или перемещения с помощью синтаксиса `move || {}`.

Это дает следующие преимущества и недостатки:

-8

Небезопасный, необоснованный, неопределённый

Небезопасность приводит к необоснованности. Необоснованность приводит к неопределённости. Неопределенный ведет к темной стороне силы.

Безопасный код

  • Безопастность имеет ограниченное значение в Rust, туманно «внутренняя профилактика неопределенного поведения (UB)».
  • Обоснованность означает, что язык не позволяет использовать себя, чтобы вызвать UB.
  • Крушение самолета или удаление базы данных не является UB, поэтому «безопасно» с точки зрения Rust.
  • Запись в `/proc/[pid]/mem` для самостоятельного изменения кода также является «безопасной», в результате чего UB не вызывается по своей сути.
-9

Неопределенное поведение (UB)

  • Как уже упоминалось, небезопасный код подразумевает особые обещания компилятору (в противном случае он не должен быть небезопасным).
  • Невыполнение какого-либо обещания (промиса) заставляет компилятор производить ошибочный код, выполнение которого приводит к UB.
  • После запуска неопределенного поведения может произойти что угодно. Коварно, эффекты могут быть 1) тонкими, 2) проявляться далеко от места нарушения или 3) быть видимыми только при определенных условиях.
  • Казалось бы, работающая программа (включая любое количество модульных тестов) не является доказательством того, что код UB не может потерпеть неудачу из за прихоти.
  • Код с UB объективно опасен, недействителен и никогда не должен существовать.
-10

Неподходящий код

  • Любой безопасный Rust код, который может (даже только теоретически) производить UB для любого пользовательского ввода, всегда является необоснованным.
  • Как и небезопасный код, который может вызывать UB по собственному желанию, нарушая вышеупомянутые обещания.
  • Необоснованный код представляет собой угрозу стабильности и безопасности и нарушает основные предположения, которые имеют многие пользователи Rust.
-11

Ответственное использование небезопасного кода

  • Не используйте `unsafe`, если вам это абсолютно не нужно.
  • Следуйте рекомендациям Nomicon Unsafe, всегда соблюдайте все правила безопасности и никогда не ссылайтесь на UB.
  • Сведите к минимуму использование `unsafe` и инкапсулируйте их в небольшие предупреждающие модули, которые легко просматривать.
  • Никогда не создавайте нездоровых абстракций, если вы не можете правильно инкапсулировать `unsafe`, не делайте этого.
  • Каждое небезопасное устройство должно сопровождаться текстовыми рассуждениями, описывающими его безопасность.

Гонка кода

Гонка кода — это безопасный код 3-й стороны, который компилируется, но не соответствует ожиданиям API и может мешать вашим собственным гарантиям (безопасности).

-12
-13

^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.