Подтвердить что ты не робот

Борьба с отношением подтипов времен жизни в Rust

Я чувствую себя глупо для просмотра раздела маркеров документации Rust и статей в Википедии о subtyping и variance, не улучшая моего понимания отношения подтипов жизни.

Я думаю, что я просто привык к типичным отношениям подтипов типа ООП типа "Cat <: Animal", что означает "Cat - подтип Animal", где "S является подтипом T" означает "любой термин S можно безопасно использовать в контексте, где ожидается срок типа T". Пока все хорошо.

Но как это относится к временам жизни? То, как оно определено сейчас в Rust, очевидно (*)

(# 1) 'a <:' b <= > время жизни a не больше, чем время жизни b.

И вы можете подумать: "Конечно, что это значит!" возможно, потому, что <: похоже на меньшее, чем оператор, или, возможно, потому, что "sub" заставляет вас думать о подмножествах, а более короткая продолжительность жизни - это, конечно, подмножество более продолжительной продолжительности жизни. Но "действительно ли это подтип" b, если "a не больше, чем" b "? Попробуйте применить определение Wikipedia отношения подтипа:

(# 2) 'a <: b <= > lifetime a можно безопасно использовать в контексте, где ожидается ожидание b.

Проблема в том, что я не могу смириться с этим. Как вы получаете от № 2 до №1? Потому что для меня это кажется противоречием... Если вы ожидаете, что что-то живое по крайней мере b, и у вас есть что-то с пожизненным временем, которое короче b, вы, очевидно, не сможете использовать его в этом контексте, когда что-то с пожизненным временем b требуется, не так ли? Является ли это только мной, или мы получили неверное отношение подтипирования для времен жизни?

Изменить: (*) Согласно Ms2ger в канале #rust IRC, это так. Он также соответствует документации на контравариантном маркере времени жизни, который используется в итераторе Items.

Edit2: маркеры ContravariantLifetime и CovariantLifetime были удалены. Теперь мы имеем PhantomData в качестве замены в модуле marker.

4b9b3361

Ответ 1

Отказ от ответственности: я не являюсь именно гуру CS, поэтому этот ответ будет посвящен практическим концепциям, и я даже не попытаюсь связать его с теоретическими концепциями, чтобы я не делал беспорядок.

Я думаю, что проблема заключается в применении концепции подтипирования к тому, что не является типом.

  • 'a - это время жизни
  • &'a T - это тип

Вы можете сравнить &'a T и &'b U и посмотреть, подчиняются ли они отношениям подтипирования, но вы не можете установить отношение субтипирования с двумя временами жизни в реферате, потому что:

  • иногда, чтобы быть подставляемым, новое время жизни должно быть больше, чем замененное время жизни.
  • иногда, чтобы быть подставляемым, новое время жизни должно быть меньше, чем замененное время жизни.

Мы можем проверить это на двух простых примерах.


Первый пример, возможно, самый простой: срок жизни может быть заменен, если он больше!

//  Using a lifetime as a bound
struct Reference<'a, T>
    where T: 'a
{
    data: &'a T
}

fn switch<'a, 'b, T>(r: &mut Reference<'a, T>, new: &'b T)
    where 'b: 'a
{
    r.data = new;
}

Здесь компилятор разрешает замену, если 'b не меньше, чем 'a, которое выражается привязкой времени жизни 'b: 'a. Это связано с тем, что Rust не терпит болтающих ссылок, и, таким образом, контейнер может содержать только ссылки на объекты, которые оживят его.

При использовании в качестве гарантии большее время жизни является подтипом меньшего времени жизни и может быть заменено вместо него. Эти намеки, как упоминалось в @aturon, что в этом использовании 'static является подтипом всех времен жизни.


Второй пример немного сложнее: срок жизни может быть заменен, если он меньше!

Начнем со следующего:

struct Token;

fn restrict<'a, 'b, T>(original: &'a T, _: &'b Token) -> &'b T
    where 'a: 'b
{
    original
}

Правильное использование:

fn main() {
    let i = 4;

    {
        let lesser = Token;
        let k = restrict(&i, &lesser);
        println!("{}", k);
    }
}

И наша предыдущая демонстрация заявила, что мы можем заменить большее время жизни вместо меньшего:

fn main() {
    let greater = Token;
    let j;  // prevent unification of lifetimes

    {
        let i = 4;
        j = restrict(&i, &greater);
    }
    println!("{}", j);
}

error: `i` does not live long enough
j = restrict(&i, &greater);

При использовании в качестве ограничения меньшее время жизни является подтипом большего времени жизни и может быть заменено вместо него. В этом использовании 'static является супертипом всех времен жизни.


Следовательно, нет единственного отношения подтипирования между сроками жизни, потому что они служат двум радикально противоположным целям!

Повторить:

  • при использовании в качестве гарантии: greater <: lesser
  • при использовании в качестве ограничения: lesser <: greater

Примечание. Некоторое время жизни может правдоподобно действовать как гарантия и ограничение одновременно.