Компромиссы проектирования
В этом разделе речь идёт о компромиссах проектирования в 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 по двум причинам:
Configможет храниться как глобальная статическая переменная (например, с использованиемOnceLock), поэтому можно создать ссылки&'static Config.- Не все механизмы конкурентности требуют времен жизни
'static, например,thread::scope.
Таким образом, данное требование, как сформулировано, исключает только типы, не реализующие Send, и мы считаем, что Варианты 1 и 3 являются правильными ответами.
Теперь попробуйте сами с вопросами ниже! Каждый раздел содержит тест, сфокусированный на одном сценарии. Пройдите тест и обязательно прочитайте контекст ответа после каждого теста.
Вместе с каждым тестом мы также предоставили ссылки на популярные крейты Rust, которые послужили источником вдохновения для теста.
Ссылки
Вдохновение: Активы Bevy, Индексы узлов Petgraph, Единицы Cargo
Деревья типов
Вдохновение: Компоненты Yew, Виджеты Druid
Диспетчеризация
Вдохновение: Системы Bevy, Запросы Diesel, Обработчики Axum