Почему Box<dyn Error> не поддерживает прямое сопоставление с вариантами конкретной ошибки?
Box<dyn Error> хранит ошибку через типаж-объект, поэтому на этапе компиляции вызывающий код знает только интерфейс Error, но не конкретный тип и его варианты. Прямое сопоставление с вариантами перечисления невозможно; для восстановления конкретного типа нужен явный downcast_ref или downcast, что слабее и менее удобно, чем сопоставление с конкретным enum.
Такой подход решает практическую задачу объединения разных типов ошибок в одном возвращаемом типе. Он особенно удобен на границах приложения, где важно быстро передать ошибку наверх, а не предоставить вызывающему коду исчерпывающую классификацию всех причин сбоя.
В Rust это компромисс между статической выразительностью и сокрытием деталей реализации. Конкретный enum сохраняет информацию о вариантах на уровне типов, а dyn Error стирает конкретный тип за интерфейсом динамической диспетчеризации.
Предположим, публичная функция возвращает Result<T, Box<dyn Error>>. Вызывающий код может вывести текст ошибки, получить её источник через source() и передать её дальше, но не может обычным match проверить варианты внутреннего перечисления.
Это ограничивает надёжную обработку ошибок: добавление или изменение конкретных типов становится скрытой деталью, а логика клиента может перейти от проверяемого компилятором сопоставления к строковому анализу или ручному приведению типа. Строковое сравнение особенно хрупко, поскольку формат сообщения не является стабильным контрактом.
Причина — стирание типа. Значение Box<dyn Error> содержит объект, доступный через методы типажа Error; его конкретный тип скрыт. Поэтому match может проверять только саму структуру оболочки, но не варианты неизвестного вызывающему коду перечисления.
downcast_ref проверяет, является ли скрытый объект конкретным типом, а downcast может забрать владение им из Box. Это требует, чтобы вызывающий код заранее знал конкретный тип; кроме того, downcast не даёт компилятору проверить, обработаны ли все возможные ошибки.
Для стабильного публичного контракта обычно выбирают собственный enum, если клиентам нужно программно различать причины сбоя. Box<dyn Error> уместнее для верхнего уровня приложения, внутренних утилит или API, где набор конкретных ошибок намеренно не является частью контракта.
Можно сочетать оба подхода: публичная библиотека возвращает конкретный тип ошибки, а граница приложения преобразует его в Box<dyn Error> для унифицированного завершения или логирования. Если используется динамический тип, полезно сохранять цепочку причин через source(), но это не заменяет типобезопасное сопоставление вариантов.
Библиотека загружает конфигурацию и может сообщить, что файл не найден, формат некорректен или значение не прошло проверку. Вариант с Box<dyn Error> прост: функции легко объединяют ошибки из разных crate, а код приложения получает единый тип. Минус — клиент не может гарантированно обработать причины через match, а downcast связывает его с внутренними типами библиотеки.
Вариант с публичным enum ConfigError требует явно описать ошибки и преобразования из нижележащих ошибок. Зато клиент может надежно сопоставлять варианты, а добавление нового варианта контролируется компилятором, если API не скрывает его через атрибуты вроде non_exhaustive.
Для библиотеки выбран конкретный enum, потому что различение ошибок нужно клиентам: отсутствие файла допускает создание конфигурации по умолчанию, а ошибка формата должна останавливать запуск. На границе бинарного приложения эти ошибки затем можно упаковать в Box<dyn Error> для общего механизма вывода. В результате библиотека сохраняет сильный контракт, а приложение не дублирует сложную типизацию всех внутренних операций.
downcast_ref полноценной заменой сопоставлению с enum?Нет. Downcast проверяет один заранее известный конкретный тип во время выполнения и не сообщает компилятору, какие другие типы или варианты существуют. Он полезен на динамической границе, но хуже подходит для исчерпывающей бизнес-логики и создаёт связанность клиента с внутренней реализацией.
Box<dyn Error> теряет исходную причину ошибки?Нет. Обёртка сохраняет сам объект ошибки, его текст и, если реализация поддерживает это, цепочку через Error::source(). Теряется не обязательно причина как данные, а статическая возможность напрямую обратиться к её конкретному типу и сопоставить все варианты на этапе компиляции.
Box<dyn Error>?Если функция объединяет большое число разнородных низкоуровневых операций, собственный enum потребует поддерживать множество вариантов и реализаций преобразования. Для внутреннего кода приложения или верхнего обработчика, которому достаточно вывести ошибку и завершить операцию, Box<dyn Error> уменьшает шаблонный код. Цена — более слабый контракт и перенос части проверок с компилятора на выполнение.