Как я могу вернуть impl Iterator, который имеет несколько времен жизни? - PullRequest
1 голос
/ 20 октября 2019

У меня проблемы с продолжительностью жизни по признаку. Я пытаюсь заставить работать следующий код:

struct Foo<'op, Input> {
    op: Box<dyn Fn(Input) -> i32 + 'op>,
}

impl<'op, Input> Foo<'op, Input> {
    fn new<Op>(op: Op) -> Foo<'op, Input>
    where
        Op: Fn(Input) -> i32 + 'op,
    {
        Foo { op: Box::new(op) }
    }

    fn apply<'input_iter, InputIter>(
        self,
        input_iter: InputIter,
    ) -> impl Iterator<Item = i32> + 'op + 'input_iter
    where
        InputIter: IntoIterator<Item = Input> + 'input_iter,
    {
        input_iter.into_iter().map(move |input| (self.op)(input))
    }
}

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

Это дает мне следующую ошибку:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/lib.rs:20:36
   |
20 |         input_iter.into_iter().map(move |input| (self.op)(input))
   |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'op as defined on the impl at 5:6...
  --> src/lib.rs:5:6
   |
5  | impl<'op, Input> Foo<'op, Input> {
   |      ^^^
   = note: ...so that the types are compatible:
           expected Foo<'_, _>
              found Foo<'op, _>
note: but, the lifetime must be valid for the lifetime 'input_iter as defined on the method body at 13:14...
  --> src/lib.rs:13:14
   |
13 |     fn apply<'input_iter, InputIter>(
   |              ^^^^^^^^^^^
note: ...so that return value is valid for the call
  --> src/lib.rs:16:10
   |
16 |     ) -> impl Iterator<Item = i32> + 'op + 'input_iter
   |          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Вот мое понимание вовлеченных жизней. Foo владеет операцией, которая является замыканием, в котором может быть ссылка где-то, поэтому у него может быть ограниченное время жизни. Это обозначено 'op, и Foo ограничен так, что он не может пережить это. Пока все хорошо.

В apply () идея состоит в том, что мы хотим использовать input_iter и self и возвращать итератор каждого элемента в input_iter, сопоставленного с помощью self.op. Аргумент input_iterator также может содержать ссылки, поэтому он может иметь свои собственные границы времени жизни, обозначаемые как «input_iter».

Я хочу вернуть итератор, который принимает владение как self, так и input_iter. При этом он должен будет принять оба их параметра времени жизни, чтобы гарантировать, что он не переживет ни ссылки input_iter, ни ссылки op. Я думал, что impl Iterator<Item = i32> + 'op + 'input_iter достигнет этого, но я, кажется, где-то сделал неправильный поворот.

Также странно, что он жалуется на закрытие. Я понимаю, что закрытие не может пережить 'операции, потому что это берет на себя ответственность оператора и его ссылок. Это имеет смысл. Чего я не понимаю, так это того, почему он должен жить так долго, как 'input_iter. Закрытие и входной итератор вообще не должны заботиться друг о друге;единственное, что их связывает, это то, что они оба имеют одного и того же владельца (выходной итератор).

Чего мне здесь не хватает?

1 Ответ

1 голос
/ 20 октября 2019

Параметры времени жизни не всегда представляют точное время жизни объекта (или заимствования). Давайте рассмотрим этот пример:

fn main() {
    let a = 3;
    let b = 5;
    let c = min(&a, &b);
    println!("{:?}", c);
}

fn min<'a>(a: &'a i32, b: &'a i32) -> &'a i32 {
    if a < b {
        a
    } else {
        b
    }
}

Здесь min принимает два ссылочных аргумента с одинаковым временем жизни. Однако мы называем это заимствованиями разных переменных, которые имеют разные времена жизни. Так почему этот код компилируется?

Ответ - Подтип и дисперсия . Большие времена жизни подтипы коротких жизней;и наоборот, более короткие времена жизни супертипы больших времен жизни. В приведенном выше примере компилятор должен найти одно время жизни, которое совместимо с обоим временем жизни ввода. Компилятор делает это, находя общий супертип обоих времен жизни (то есть самое короткое время жизни, которое содержит оба). Это называется унификация .


Назад к вашей проблеме. Похоже, что компилятор в настоящий момент не слишком хорошо обрабатывает несколько границ времени жизни для impl Trait . Однако в действительности нет необходимости иметь две границы срока службы: достаточно одного. Компилятор сократит этот единственный срок службы, если это необходимо для удовлетворения требований self и input_iter.

struct Foo<'op, Input> {
    op: Box<dyn Fn(Input) -> i32 + 'op>,
}

impl<'op, Input> Foo<'op, Input> {
    fn new<Op>(op: Op) -> Foo<'op, Input>
    where
        Op: Fn(Input) -> i32 + 'op,
    {
        Foo { op: Box::new(op) }
    }

    fn apply<InputIter>(
        self,
        input_iter: InputIter,
    ) -> impl Iterator<Item = i32> + 'op
    where
        Input: 'op,
        InputIter: IntoIterator<Item = Input> + 'op,
    {
        input_iter.into_iter().map(move |input| (self.op)(input))
    }
}

fn main() {
    let y = 1;
    let foo = Foo::new(|x| x as i32 + y);

    let s = "abc".to_string();
    let sr: &str = &*s;
    let bar = sr.chars();

    let baz: Vec<_> = foo.apply(bar).collect();
    println!("{:?}", baz);
}

Попробуйте переместить строки в main, чтобы убедиться, что времена жизни действительно объединены.

На этом этапе называть время жизни 'op несколько вводящим в заблуждение;с тем же успехом его можно назвать 'a.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...