Это имеет смысл, и это не ошибка компилятора, но это несколько неудобно.
Полное объяснение
Возможно реализовать Index<T>
для типа C
, такого, что C::Output
имеет тип, который должен пережить некоторое время жизни, от внутреннего до T
.Вот глупый пример:
struct IntRef<'a>(&'a i32);
impl<'a, 'b: 'a> Index<IntRef<'a>> for IntRef<'b> {
type Output = IntRef<'a>;
fn index(&self, _: IntRef<'a>) -> &Self::Output {
self
}
}
Одеяло impl
будет пытаться реализовать Foo<IntRef<'a>>
для IntRef<'b>
, что несостоятельно.Чтобы понять почему, взгляните на этот пример без компиляции:
let b = 2i32; // 'b begins here:
let b_ref = IntRef(&b);
let o: &Display; // a reference that outlives 'a but not 'b
{
let a = 1i32; // 'a begins here:
let a_ref = IntRef(&a);
o = &b_ref[a_ref]; // <-- this errors: "a doesn't live long enough"
// which is correct!
o = b_ref.foo(a_ref); // <-- this wouldn't error, because the returned
// value is `&'x (Display + 'x)` where 'x is
// the lifetime of `b_ref`
}
println!("{:?}", o);
o = &b_ref[a_ref];
не скомпилируется, потому что Index
реализован так, что b_ref[a_ref]
не может пережить a_ref
.Но o = b_ref.foo(a_ref)
необходимо скомпилировать, поскольку определение Foo<T>::foo
...
fn foo(&self, i: T) -> &Display // what you wrote
fn foo<'a>(&'a self, i: T) -> &'a ('a + Display) // what the compiler inferred
... обеспечивает, что время жизни выходных данных зависит только от * на время жизни &self
(см. этот вопрос ).Компилятор отклоняет общую реализацию Foo
, потому что если бы она была разрешена, вы могли бы использовать ее для «увеличения» времени жизни, такого как a_ref
в приведенном выше примере.
(я не смог придуматьспособ сделать IntRef
практичным, но факт остается фактом, что вы могли бы сделать это. Возможно, из-за внутренней изменчивости, достаточно умный человек мог внести несостоятельность, если бы это было разрешено.)
Решение 0: Быстро и грязно
Просто потребуйте, чтобы T
никогда не содержало (не 'static
) ссылок, и ваша работа выполнена.
impl<C, T> Foo<T> for C
where
T: 'static,
C: Index<T>,
C::Output: Display + Sized,
Это, вероятно,в случае наиболее распространенного использования черты Index
, но если вы хотите иметь возможность реализовать Foo<&T>
(что не является необоснованным), вам нужно попробовать что-то немного менее ограничительное.
Другая возможность требует, чтобы C::Output
было 'static
, но это опять-таки более консервативно, чем необходимо.
Решение 1. Лучший путь
Давайте вернемся к десагерингу Foo::foo
:
fn foo<'a>(&'a self, i: T) -> &'a ('a + Display)
Обратите внимание на два 'a
s в &'a ('a + Display)
.Хотя они одинаковы, они представляют разные вещи: (максимальное) время жизни возвращаемой ссылки и (максимальное) время жизни любых ссылок, содержащихся внутри вещи, на которую ссылаются.
В Index
, что мы используем для реализации Foo
, время жизни возвращаемой ссылки всегда связано с заимствованием &self
.Но Self::Output
может содержать другие ссылки с другим (возможно, более коротким) сроком службы, что является всей проблемой.Так что мы действительно хотели бы написать ...
fn foo(&self, i: T) -> &('a + Display) // what you write
fn foo<'b>(&'b self, i: T) -> &'b ('a + Display) // what the compiler infers
... который отделяет время жизни &self
от любых времен жизни, которые могут быть внутренними, до Self::Output
.
Конечно, сейчас проблема в том, что 'a
нигде не определен в черте, поэтому мы должны добавить его в качестве параметра:
trait Foo<'a, T> {
fn foo(&self, i: T) -> &('a + Display);
}
Теперь вы можете сказать Rust, что C::Output
должен пережить'a
для применения impl
, и все будет хорошо ( детская площадка ):
impl<'a, C, T> Foo<'a, T> for C
where
C: Index<T>,
C::Output: 'a + Display + Sized,
{
fn foo(&self, i: T) -> &('a + Display) {
&self[i]
}
}
Решение 2. Установите ограничение для метода
Решение 1 требует добавления параметра времени жизни к Foo
, что может быть нежелательно.Другой возможностью является добавление предложения where
к foo
, для которого требуется T
, чтобы пережить возвращенное &Display
.
trait Foo<T> {
fn foo<'a>(&'a self, i: T) -> &'a Display where T: 'a;
}
Это немного неуклюже, но эффективно позволяет перенести требование нафункция, а не сама черта.Недостатком является то, что этот также исключает некоторые реализации Foo
, настаивая на том, что возвращаемое значение никогда не переживает любую ссылку в T
.