В практическом API объект хранится как Box<dyn Trait>, но реализация трейта содержит ссылку на данные вызывающего кода. Какой lifetime-bound потребуется и почему?
Потребуется явно указать lifetime объекта трейта, например Box<dyn Trait + 'a>. Запись Box<dyn Trait> в таком контексте обычно означает Box<dyn Trait + 'static>, поэтому объект не может содержать ссылку, действительную только до 'a.
'static здесь означает не обязательное хранение объекта всю жизнь программы, а отсутствие заимствований короче 'static внутри скрытого типа. Явный 'a связывает срок жизни trait object со сроком жизни заимствованных данных.
Trait objects позволяют работать с неизвестным на этапе компиляции конкретным типом через общий интерфейс. Это нужно для динамической диспетчеризации и хранения разных реализаций трейта в одной структуре, но указатель на такой объект должен иметь понятные правила владения и заимствования.
В Rust срок жизни заимствований является частью проверки безопасности памяти. Поэтому у dyn Trait есть не только набор методов, но и lifetime-bound, определяющий, как долго скрытый конкретный тип может содержать заимствованные данные.
Если структура владеет Box<dyn Trait>, она владеет контейнером, но не обязательно всеми данными, на которые ссылается скрытый тип. Без lifetime-bound компилятор не может проверить, что эти данные переживут объект трейта.
Неявный 'static может неожиданно запретить корректную передачу временно заимствованных данных. Обратная ошибка — выбрать слишком короткий lifetime и тем самым ограничить срок хранения объекта сильнее, чем требуется API.
В распространённой записи Box<dyn Trait> lifetime trait object выводится как 'static, потому что Box сам по себе не задаёт lifetime-bound для своего параметра типа. Поэтому тип вроде Box<dyn Display> может содержать только конкретный тип, не имеющий заимствований короче 'static.
Если объект должен ссылаться на данные с lifetime 'a, bound задают явно: Box<dyn Trait + 'a>. Тогда компилятор проверяет, что объект уничтожается не позже данных, на которые он ссылается.
Минимальный рабочий пример:
Здесь Borrowed<'a> хранит ссылку на text, поэтому Box<dyn Show + 'a> корректно связывает срок жизни trait object с 'a. Если заменить возвращаемый тип на Box<dyn Show>, компилятор будет требовать, чтобы скрытый тип удовлетворял 'static.
Lifetime самого указателя и lifetime внутри trait object — связанные, но разные понятия. Например, &'a dyn Trait описывает, как долго существует ссылка на объект, а dyn Trait + 'a — как долго могут жить заимствования внутри скрытого типа.
Практический компромисс таков: 'static упрощает владение и часто подходит для потоков, очередей и долгоживущих хранилищ; параметризованный 'a позволяет избежать лишнего копирования данных, но распространяет lifetime через сигнатуры функций и структур.
Сервис формирует набор обработчиков, часть которых владеет настройками, а часть временно ссылается на конфигурацию запроса. Разработчик объявляет поле как Vec<Box<dyn Handler>> и получает ошибку при добавлении обработчика со ссылкой на запрос.
Вариант с принудительным 'static прост: обработчики могут владеть данными через String, Arc или другие owned-типы. Плюс — простые границы API и возможность безопасно передавать объекты между долгоживущими компонентами; минус — возможны копирование и дополнительное владение.
Вариант с Vec<Box<dyn Handler + 'a>> сохраняет заимствования без копирования. Он эффективнее по памяти, но вся структура и функции, работающие с ней, должны получить lifetime 'a, а хранить такую структуру дольше источника данных нельзя.
Если набор обработчиков действительно живёт только в пределах обработки одного запроса, выбирают параметризованный 'a. Если он помещается в глобальное состояние или очередь между потоками, обычно выбирают owned-данные и 'static, потому что это лучше соответствует сроку жизни компонента.
Box<dyn Trait + 'static> должно жить до завершения программы?Нет. 'static является bound на заимствования внутри конкретного типа, а не приказом навсегда хранить сам объект. Владеющий объект без ссылок можно уничтожить сразу после использования; он просто не содержит данных с коротким lifetime.
'a к dyn Trait не продлевает исходные данные?Lifetime-bound только сообщает компилятору допустимый срок использования и проверяет его. Он не увеличивает срок жизни ссылки и не превращает локальную переменную в долгоживущую. Источник данных должен реально пережить Box<dyn Trait + 'a>.
Box<dyn Trait + 'a> на &'a dyn Trait?Не всегда. &'a dyn Trait заимствует уже существующий объект и не владеет им, тогда как Box<dyn Trait + 'a> владеет выделенным объектом, но тот может заимствовать внешние данные до 'a. Первый вариант требует готового значения и ограничивает модель владения; второй позволяет создать trait object внутри функции, но требует корректного владения самим контейнером.