Время жизни метода, возвращающего итератор структур с одинаковым временем жизни - PullRequest
0 голосов
/ 15 мая 2018

Предположим следующий надуманный пример:

struct Board {
    squares: Vec<i32>,
}

struct Point<'a> {
    board: &'a Board,
    x: i32,
    y: i32,
}

impl<'a> Point<'a> {
    pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
        [(0, -1), (-1, 0), (1, 0), (1, 0)]
            .iter().map(|(dx, dy)| Point {
                board: self.board,
                x: self.x + dx,
                y: self.y + dy,
            })
    }
}

Это не компилируется, потому что из того, что я понимаю, время жизни точек, созданных в лямбда-выражениях, неверно:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:14:25
   |
14 |               .iter().map(|(dx, dy)| Point {
   |  _________________________^
15 | |                 board: self.board,
16 | |                 x: self.x + dx,
17 | |                 y: self.y + dy,
18 | |             })
   | |_____________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 12:5...
  --> src/main.rs:12:5
   |
12 | /     pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
13 | |         [(0, -1), (-1, 0), (1, 0), (1, 0)]
14 | |             .iter().map(|(dx, dy)| Point {
15 | |                 board: self.board,
...  |
18 | |             })
19 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &&Point<'_>
              found &&Point<'a>
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 11:1...
  --> src/main.rs:11:1
   |
11 | impl<'a> Point<'a> {
   | ^^^^^^^^^^^^^^^^^^
note: ...so that return value is valid for the call
  --> src/main.rs:12:32
   |
12 |     pub fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Я немного растерялся относительно того, почему это так, потому что кажется, что жизни здесь имеют смысл. Время жизни Point обусловлено временем жизни ссылки на Board. Таким образом, Point<'a> имеет ссылку на доску с временем жизни 'a, поэтому он должен иметь возможность создавать больше Point<'a> s, поскольку их ссылки на доски будут иметь одинаковое время жизни ('a).

Но, если я уберу лямбду, это сработает:

impl<'a> Point<'a> {
    pub fn neighbors(&self) -> [Point<'a>; 4] {
        [
            Point { board: self.board, x: self.x    , y: self.y - 1},
            Point { board: self.board, x: self.x - 1, y: self.y    },
            Point { board: self.board, x: self.x + 1, y: self.y    },
            Point { board: self.board, x: self.x    , y: self.y + 1},
        ]
    }
}

Итак, я подозреваю, что проблема заключается в том, что лямбда может быть запущена после того, как закончится время жизни 'a. Но значит ли это, что я не могу лениво выдавать эти очки?

tl; dr Как сделать так, чтобы средство проверки заимствований было удовлетворено методом, который лениво создает новые структуры, время жизни которых связано со структурой, создающей их?

Ответы [ 3 ]

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

Я бы присвоил self время жизни, отличное от времени жизни Board:

impl<'b> Point<'b> {
    fn neighbors<'a>(&'a self) -> impl Iterator<Item = Point<'b>> + 'a {
        [(0, -1), (-1, 0), (1, 0), (1, 0)]
            .iter().map(move |(dx, dy)| Point {
                board: self.board,
                x: self.x + dx,
                y: self.y + dy,
            })
    }
}

Для этого также необходимо пометить замыкание как move, чтобы переместить значение &Selfв закрытие, так что закрытие все еще может получить доступ к board, x и y, когда оно продвигается.Поскольку неизменяемые ссылки могут быть скопированы, это не помешает вызывающей стороне делать что-либо.

Без отдельных времен жизни, времена Point s, возвращаемые из итератора, искусственно ограничиваются временем жизни Point что породило их.Например, этот код не работает, когда время жизни унифицировано, но работает, когда они различны:

fn example<'b>(board: &'b Board) {
    let _a = {
        let inner = Point { board, x: 0, y: 0 };
        let mut n = inner.neighbors();
        n.next().unwrap()
    };
}

См. Также:

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

Оба существующих ответа ( Shepmaster , Boiethios ) позволяют Point s, возвращенным итератором, пережить сам итератор.Однако должна быть возможность создать итератор, который даже переживает исходный Point, из которого он был создан, путем перемещения в него содержимого Point.Эта версия сохраняет исходную сигнатуру функции:

fn neighbors(&self) -> impl Iterator<Item = Point<'a>> {
    let Point { board, x, y } = *self;
    [(0, -1), (-1, 0), (1, 0), (1, 0)]
        .iter().map(move |(dx, dy)| Point {
            board: board,
            x: x + dx,
            y: y + dy,
        })
}

Копирование содержимого *self в локальные переменные, которые перемещаются в замыкание, делает его закрытием - и, следовательно, возвращаемым итератором - больше не содержитлюбые ссылки на self.

Вот что вы можете сделать с этой версией, чего нельзя сделать иначе ( детская площадка ):

let mut p = Point {
    board: &b,
    x: 10,
    y: 12,
};
for n in p.neighbors() {
    p = n;
}

Одно потенциальное предостережениеявляется то, что если Point содержит очень большие или не Copy данные, которые не могут или не должны быть перемещены в замыкание, это не сработает.В этом случае вы должны использовать другое решение:

fn neighbors<'b>(&'b self) -> impl 'b + Iterator<Item = Point<'a>>
0 голосов
/ 15 мая 2018

Если у вас есть такая проблема в методе, вы должны добавить явное время жизни к &self:

pub fn neighbors(&'a self) -> impl Iterator<Item = Point<'a>> {
    [(0, -1), (-1, 0), (1, 0), (1, 0)]
        .iter().map(|(dx, dy)| Point {
            board: self.board,
            x: self.x + dx,
            y: self.y + dy,
        })
}

Ошибка теперь лучше

error[E0373]: closure may outlive the current function, but it borrows `self`, which is owned by the current function
  --> src/main.rs:14:30
   |
14 |             .iter().map(|(dx, dy)| Point {
   |                         ^^^^^^^^^^ may outlive borrowed value `self`
15 |                 board: self.board,
   |                        ---- `self` is borrowed here
help: to force the closure to take ownership of `self` (and any other referenced variables), use the `move` keyword
   |
14 |             .iter().map(move |(dx, dy)| Point {
   |                         ^^^^^^^^^^^^^^^

Затем вам просто нужно добавить ключевое слово move в соответствии с рекомендациями компилятора, чтобы сказать ему, что вы больше не будете использовать &'a self.

Обратите внимание, что время жизни self небыть таким же, как время жизни Point.Это лучше использовать эту подпись:

fn neighbors<'b>(&'b self) -> impl 'b + Iterator<Item = Point<'a>>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...