Добавление несвязанного универсального параметра вызывает странную ошибку времени жизни - PullRequest
0 голосов
/ 28 мая 2018

У меня есть черта, и я хочу реализовать ее для всех типов, которые реализуют std::ops::Index.Этот код работает (как я и ожидал):

use std::ops::Index;
use std::fmt::Display;


trait Foo {
    fn foo(&self, i: usize) -> &Display;
}

impl<C> Foo for C 
where
    C: Index<usize>,
    C::Output: Display + Sized,
{
    fn foo(&self, i: usize) -> &Display {
        &self[i]
    }
}

( Детская площадка )

Однако , как только я введу универсальный параметр в мойчерта, я получаю странную ошибку при жизни.Это код ( Детская площадка ):

trait Foo<T> {
    fn foo(&self, i: T) -> &Display;
}

impl<C, T> Foo<T> for C 
where
    C: Index<T>,
    C::Output: Display + Sized,
{
    fn foo(&self, i: T) -> &Display {
        &self[i]
    }
}

И странная ошибка (очевидно, это одна ошибка, повторенная три раза в слегка отличающихся версиях):

  error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the type `<C as std::ops::Index<T>>::Output` is not borrowed for too long
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^

error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the type `<C as std::ops::Index<T>>::Output` will meet its required lifetime bounds
  --> src/main.rs:15:9
   |
15 |         &self[i]
   |         ^^^^^^^^

error[E0311]: the associated type `<C as std::ops::Index<T>>::Output` may not live long enough
  --> src/main.rs:15:10
   |
15 |         &self[i]
   |          ^^^^^^^
   |
   = help: consider adding an explicit lifetime bound for `<C as std::ops::Index<T>>::Output`
note: the associated type `<C as std::ops::Index<T>>::Output` must be valid for the anonymous lifetime #1 defined on the method body at 14:5...
  --> src/main.rs:14:5
   |
14 | /     fn foo(&self, i: T) -> &Display {
15 | |         &self[i]
16 | |     }
   | |_____^
note: ...so that the reference type `&<C as std::ops::Index<T>>::Output` does not outlive the data it points at
  --> src/main.rs:15:10
   |
15 |         &self[i]
   |          ^^^^^^^

Я вообще не понимаю ошибку.Тем более, что ошибка говорит о времени жизни C::Output, которое (насколько я понимаю) не имеет ничего , имеющего отношение к дополнительному параметру K.

Интересно, что объект возврата не возвращается&Display, но добавление связанного типа к Foo, который возвращается, устраняет ошибку времени жизни ( Playground ).Однако это не решение для меня.


Что означает эта ошибка?Имеет ли это смысл?Это ошибка компилятора?Какое отношение имеет параметр K к времени жизни C::Output?

1 Ответ

0 голосов
/ 28 мая 2018

Это имеет смысл, и это не ошибка компилятора, но это несколько неудобно.

Полное объяснение

Возможно реализовать 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.

...