Чем различаются panic! и panic_any! с точки зрения типа payload, доступного после перехвата паники?
panic_any! позволяет передать при панике произвольное значение, удовлетворяющее требованиям Send + 'static; после catch_unwind его можно извлечь через проверку конкретного типа. panic! предназначен прежде всего для формирования сообщения о панике, поэтому код не должен полагаться на конкретный тип его payload.
Механизм payload появился как способ передать диагностическую информацию через границу раскрутки стека. Это полезно для инфраструктурного кода, который перехватывает панику и должен либо записать сведения, либо классифицировать причину аварийного завершения.
При этом паника не является обычным каналом обработки ожидаемых ошибок. Result описывает ошибки в контракте функции, а payload паники предназначен для аварийных ситуаций и внутренних границ восстановления.
После catch_unwind вызывающий код получает ошибку как Box<dyn Any + Send>. Если считать, что внутри всегда находится строка, произвольный payload будет недоступен или обработка завершится новым сбоем при неправильном приведении типа.
Неверное проектирование также возникает, когда библиотека использует payload паники как публичный API. Формат сообщения и конкретный тип значения, созданного panic!, не следует рассматривать как стабильный контракт для бизнес-логики.
panic_any! принимает значение произвольного конкретного типа, который можно безопасно передать между потоками и который не содержит неограниченных заимствований. При раскрутке стека это значение помещается в тип-стирающую оболочку Box<dyn Any + Send>.
Для извлечения применяют downcast_ref::<T>() или downcast::<T>(). Первый метод возвращает ссылку и сохраняет владение payload у оболочки, второй пытается вернуть владение конкретным значением.
panic! формирует панику с сообщением. В зависимости от формы вызова внутреннее представление сообщения может отличаться, поэтому безопасная обработка должна проверять тип динамически, а не безусловно выполнять downcast к String или &str.
Оба механизма подчиняются общей политике panic runtime: при panic = "unwind" payload может быть перехвачен на границе catch_unwind, а при panic = "abort" раскрутки стека нет и перехват не сработает. Даже при раскрутке catch_unwind не делает произвольную функцию безопасной для продолжения работы: состояние программы могло быть нарушено.
Здесь panic_any! сохраняет именно Vec<i32>, поэтому его можно получить через downcast_ref. Для обычной ошибки операции такой дизайн хуже Result, поскольку компилятор не заставляет вызывающий код обрабатывать возможные варианты.
Тестовый раннер изолирует пользовательские тесты и должен записывать не только факт паники, но и структурированные данные, например идентификатор теста и диагностические метаданные. Вариант с panic!("...") прост, но требует разбора текста и ломается при изменении формулировки сообщения.
Вариант с Result хорошо подходит для ожидаемых ошибок теста, но не перехватывает код, который аварийно завершился через панику. Безусловный downcast payload к одному строковому типу также ненадёжен: другая часть программы может использовать другой тип.
Практическое решение — перехватить панику на изолирующей границе, сначала попытаться извлечь поддерживаемый структурированный тип через downcast_ref, а для остальных payload использовать резервное диагностическое представление. Ожидаемые сбои при этом остаются Result, а panic_any! применяется только для специальной внутренней передачи данных. Такой подход не связывает основной API с текстом паники и сохраняет возможность безопасно обработать неизвестные payload.
1. Вопрос: Почему нельзя безусловно преобразовать payload паники в String?
Ответ: Payload имеет тип dyn Any + Send, а не String. Даже если конкретный вызов паники был связан с текстом, это не означает, что внутри всегда находится именно String: представление может быть другим. Нужно использовать проверяемый downcast и предусматривать fallback для неизвестного типа.
2. Вопрос: Чем отличаются downcast_ref и downcast при обработке payload?
Ответ: downcast_ref::<T>() проверяет тип и возвращает Option<&T>, поэтому payload остаётся во владении Box<dyn Any + Send>. downcast::<T>() потребляет коробку и при успехе передаёт вызывающему владение значением T; при неудаче возвращает исходную коробку. Выбор зависит от того, нужно ли только прочитать данные или передать их дальше.
3. Вопрос: Почему структурированный payload паники не превращает panic в полноценный аналог Result?
Ответ: Сигнатура функции не сообщает вызывающему коду, какие payload могут возникнуть и где именно возможна паника. Компилятор не требует обработать такие случаи, а сама паника может оставить состояние программы непригодным для продолжения. Поэтому структурированный payload оправдан на узких инфраструктурных границах, тогда как ожидаемые ошибки следует выражать через Result.