Изменяет ли срок действия тип ссылки?

Я недавно начал изучать Rust и только что закончил читать LifetimeKata после прочтения книги . Мне кажется, что я понял все, кроме головоломки из главы 9, в которой упоминается эта проблема GitHub для Rusc.

По сути, следующий пример не скомпилируется, несмотря на то, что он кажется корректным.

use std::collections::HashSet;

struct Difference<'fst, 'snd>(Vec<&'fst str>, Vec<&'snd str>);

fn diffwords<'fst, 'snd>(lhs: &'fst str, rhs: &'snd str) -> Difference<'fst, 'snd> {
    let lhs: HashSet<&str> = lhs.split(' ').collect();
    let rhs: HashSet<&str> = rhs.split(' ').collect();
    Difference(
        lhs.difference(&rhs).copied().collect(),
        rhs.difference(&lhs).copied().collect(),
    )
}

fn main() {
    let lhs = String::from("I love the surf and the sand.");

    let res = {
        let rhs = String::from("I hate the surf and the sand.");
        diffwords(&lhs, &rhs).0
    };

    assert_eq!(res, vec!["love"]);

    let res = {
        let rhs = String::from("I love the snow and the sand.");
        diffwords(&rhs, &lhs).1
    };

    assert_eq!(res, vec!["surf"]);
}

Этот комментарий , похоже, согласен с автором проблемы GitHub, что существует проблема в реализации HashSet<T, S>::difference(), но упоминает, что она вряд ли будет исправлена ​​для поддержания обратной совместимости с предыдущими версиями Rust. Между тем, этот другой комментарий, кажется, говорит о том, что поведение именно такое, каким оно должно быть согласно сигнатуре метода difference() (скопировано ниже), где Self в данном случае — это HashSet<T, S>.

fn difference<'a>(&'a self, other: &'a HashSet<T, S>) -> Difference<'a, T, S>

Мое замешательство связано с этим утверждением второго комментатора.

API применяется только к одному и тому же типу T, т. е. для ссылочного типа время жизни обоих хеш-наборов должно быть одинаковым.

Правильно ли я понимаю, что второй комментатор предполагает, что две ссылки одного и того же типа на самом деле являются разными типами, если они имеют разные границы времени жизни? Другими словами, являются ли &'a T и &'b T двумя разными типами? И если да, то почему?

&'a T и &'b T — разные типы по той же причине, Foo<Bar> и Foo<Baz> — разные типы. Имеет ли последнее для вас смысл? &'a T — это, по сути, причудливый синтаксис для гипотетического типа Ref<'a, T> ссылки с универсальными параметрами 'a и T.
cafce25 11.04.2024 08:03

Ограничение на время жизни не меняет тип. Другая жизнь делает это (время жизни ограничено T: 'a).

Chayim Friedman 11.04.2024 20:57

Да, я думаю, я могу уяснить тот факт, что тип ссылки также зависит от ее времени жизни. Я предполагаю, что это дизайнерское решение, которое выбрали создатели Rust, чтобы упростить реализацию гарантий безопасности, которые предоставляет Rust. Однако из любопытства: нельзя ли реализовать эти гарантии безопасности без этого ограничения? Станет ли это намного сложнее? И является ли это ограничением, которое может быть смягчено в будущих версиях Rust?

jinscoe123 15.04.2024 22:32
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
3
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваша путаница может возникнуть из-за того, что во многих случаях компилятор может неявно преобразовать &'a T в &'b T (например, если 'b короче 'a). Но это по-прежнему разные типы, и компилятор не всегда может преобразовать их. Например:

fn uses_static (_foo: &'static i32) {}

fn calls_static<'a> (foo: &'a i32) {
    uses_static (foo);
}

Приведенный выше код приводит к ошибке «ссылка живет недостаточно долго», поскольку &'a i32 отличается от &'static i32. Это важно, потому что uses_static разрешено хранить _foo в глобальной переменной (потому что это 'static), что может вызвать ошибки, если мы попытаемся передать локальную переменную:

static GLOBAL: Mutex<&'static i32> = Mutex::new (&0);

fn uses_static (foo: &'static i32) {
    *GLOBAL.lock().unwrap() = foo;
}

fn calls_static() {
    let foo = 42;
    // `&foo` has type `&'a i32` but `uses_static` expects a `&'static i32`
    // so this is not allowed.
    uses_static (&foo);
}

fn invalid_access() {
    calls_static();
    // Invalid memory access! We're trying to read the  value of `foo`
    // but this was dropped when `calls_static` returned
    println!("{}", **GLOBAL.lock().unwrap());
}

С другой стороны:

fn uses_two_refs<'b> (_x: &'b i32, _y: &'b i32) {}

fn uses_one_ref<'a> (x: &'a i32) {
    let y = 0;    // This has lifetime `'b` strictly shorter than `'a`
    uses_two_refs (x, &y);
}

В этом третьем примере &'a i32 и &'b i32 имеют разные типы, поэтому вызов uses_two_refs (x, &y) должен завершиться неудачей, поскольку x и &y имеют разное время жизни. Но в этом случае компилятор может неявно преобразовать x в тип &'b i32, поскольку 'b короче, чем 'a.

См. также главу «Подтипирование и вариативность» справочника по Rust.

Я думаю, что мое замешательство в основном связано с тем, что я работаю на небезопасных языках, таких как C/C++, где тип указателя/ссылки не зависит от его времени жизни. Кроме того, мне любопытно узнать, является ли это необходимым ограничением, налагаемым компилятором Rust, или оно просто упрощает реализацию его гарантий безопасности.

jinscoe123 15.04.2024 22:39

Другие вопросы по теме