Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Компромиссы проектирования

В этом разделе речь идёт о компромиссах проектирования в Rust. Чтобы быть эффективным инженером Rust, недостаточно просто знать, как работает Rust. Вы должны решать, какие из множества инструментов Rust подходят для конкретной задачи. В этом разделе мы предложим вам серию тестов на понимание компромиссов проектирования в Rust. После каждого теста мы подробно объясним нашу логику для каждого вопроса.

Вот пример того, как будет выглядеть вопрос. Он начинается с описания примера программного обеспечения и пространства проектных решений:

Контекст: Вы разрабатываете приложение с глобальной конфигурацией, например, содержащей флаги командной строки.

Функциональность: Приложению необходимо передавать неизменяемые ссылки на эту конфигурацию по всему приложению.

Проектные варианты: Ниже представлены несколько предложенных вариантов реализации функциональности.

use std::rc::Rc;
use std::sync::Arc;

struct Config { 
    flags: Flags,
    // .. more fields ..
}

// Вариант 1: использовать ссылку
struct ConfigRef<'a>(&'a Config);

// Вариант 2: использовать указатель с подсчётом ссылок
struct ConfigRef(Rc<Config>);

// Вариант 3: использовать атомарный указатель с подсчётом ссылок
struct ConfigRef(Arc<Config>);

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

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

Требование: Ссылка на конфигурацию должна быть общей (shareable) между несколькими потоками.

Ответ:

Вариант 1
Вариант 2
Вариант 3

В формальных терминах это означает, что ConfigRef реализует Send и Sync. Предполагая, что Config: Send + Sync, тогда и &Config, и Arc<Config> удовлетворяют этому требованию, но Rc — нет (потому что неатомарные указатели с подсчётом ссылок не являются потокобезопасными). Таким образом, Вариант 2 не удовлетворяет требованию, а Вариант 3 — удовлетворяет.

Мы также можем быть склонны заключить, что Вариант 1 не удовлетворяет требованию, потому что такие функции, как thread::spawn, требуют, чтобы все данные, перемещаемые в поток, содержали только ссылки с временем жизни 'static. Однако это не исключает Вариант 1 по двум причинам:

  1. Config может храниться как глобальная статическая переменная (например, с использованием OnceLock), поэтому можно создать ссылки &'static Config.
  2. Не все механизмы конкурентности требуют времен жизни 'static, например, thread::scope.

Таким образом, данное требование, как сформулировано, исключает только типы, не реализующие Send, и мы считаем, что Варианты 1 и 3 являются правильными ответами.


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

Вместе с каждым тестом мы также предоставили ссылки на популярные крейты Rust, которые послужили источником вдохновения для теста.

Ссылки

Вдохновение: Активы Bevy, Индексы узлов Petgraph, Единицы Cargo

Деревья типов

Вдохновение: Компоненты Yew, Виджеты Druid

Диспетчеризация

Вдохновение: Системы Bevy, Запросы Diesel, Обработчики Axum

Промежуточные представления

Вдохновение: Serde и miniserde