Я недавно начал изучать 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
двумя разными типами? И если да, то почему?
Ограничение на время жизни не меняет тип. Другая жизнь делает это (время жизни ограничено T: 'a
).
Да, я думаю, я могу уяснить тот факт, что тип ссылки также зависит от ее времени жизни. Я предполагаю, что это дизайнерское решение, которое выбрали создатели Rust, чтобы упростить реализацию гарантий безопасности, которые предоставляет Rust. Однако из любопытства: нельзя ли реализовать эти гарантии безопасности без этого ограничения? Станет ли это намного сложнее? И является ли это ограничением, которое может быть смягчено в будущих версиях Rust?
Ваша путаница может возникнуть из-за того, что во многих случаях компилятор может неявно преобразовать &'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, или оно просто упрощает реализацию его гарантий безопасности.
&'a T
и&'b T
— разные типы по той же причине,Foo<Bar>
иFoo<Baz>
— разные типы. Имеет ли последнее для вас смысл?&'a T
— это, по сути, причудливый синтаксис для гипотетического типаRef<'a, T>
ссылки с универсальными параметрами'a
иT
.