Я играл с деревьями и чертами и столкнулся с поведением, которое не понял. Вот минимальный (не компилируемый) пример:
trait Trait<T> {}
struct Struct<T> {
option: Option<Box<dyn Trait<T>>>, // change this to Option<Box<TestStruct<T>>>
// and it works without issues
}
impl<T> Trait<T> for Struct<T> {}
fn set<T>(s: &mut Struct<T>) { // works when changed to "fn set <T: 'static> ..."
s.option = Some(Box::new(Struct { option: None })) // "error[E0310]: the parameter type `T` may not live long enough"
}
Так что этот код работает либо с T: 'static
, либо с Option<Box<TestStruct<T>>>
, но не так, как есть, и я не смог найти удовлетворительного объяснения, почему.
Может кто-нибудь объяснить, что случилось с чертами и сроками жизни? Есть ли другой способ сделать это?
Всякий раз, когда мы используем трейт-объект, здесь dyn Trait<T>
, мы отказываемся от большого количества информации, которая обычно есть в Rust о характеристиках типа.
В частности, значения типа могут содержать ссылки, и в этом случае этот тип имеет одно или несколько жизненных циклов, которые определяют, как долго эти ссылки действительны. (Самый простой случай, когда тип сам является ссылкой; &'a str
не может существовать дольше, чем время окончания, указанное 'a
.
Но трейт-объект не определяет конкретный тип; это может быть любой тип, реализующий черту. Это означает, что трейт-объекты всегда должны учитывать, какое время жизни может содержать конкретный тип. Это делается с помощью «срока жизни» трейт-объекта. У каждого трейт-объекта есть срок жизни — он просто часто не записывается благодаря пожизненной элизии.
В частности, правила исключения говорят о том, что каждый раз, когда вы пишете Box<dyn Trait>
, это эквивалентно Box<dyn Trait + 'static>
. То есть любой тип X
, который вы хотите превратить в этот типаж-объект, должен соответствовать границе X: 'static
, что означает, что если он содержит какие-либо времена жизни, они должны пережить 'static
— что в конкретном случае времени жизни 'static
совпадает с говоря, что они должны быть равны 'static
, потому что ни одна жизнь не длиннее 'static
.
(Другой распространенный случай ограничения времени жизни трейт-объекта по умолчанию — это &'a dyn Trait
, который согласно правилам эквивалентен &'a (dyn Trait + 'a)
— разрешению всего, что пережило или равно времени жизни ссылки.)
Итак, объясняя то, что вы заметили:
Добавление T: 'static
работает, потому что ваш TestStruct<T>
(определение которого вы не дали, как я предполагаю) не содержит ссылок, поэтому T: 'static
логически подразумевает TestStruct<T>: 'static
, что удовлетворяет ограничению времени жизни типаж-объекта.
Использование Option<Box<TestStruct<T>>>
вместо Option<Box<dyn Trait<T>>>
означает, что здесь нет задействованного трейт-объекта, поэтому нет привязки 'static
по умолчанию, поэтому Struct<T>
является полностью общим — ему все равно, есть ли у T
время жизни или нет.
В большинстве случаев, когда вы хотите использовать Box<dyn Trait>
— для хранения в какой-либо структуре данных — привязка T: 'static
является правильным выбором, потому что вещи, которые вы собираетесь хранить на потом, обычно не должны быть привязаны к определенному сроку службы — жить вечно. Но если вам нужно было допустить типы с нестатическим временем жизни, вы всегда можете вместо этого написать Box<dyn Trait + 'a>
(при условии, что время жизни 'a
объявлено).