Когда безопасно вывести значение члена из закрепленного будущего? - PullRequest
22 голосов
/ 09 мая 2019

Я пишу будущий комбинатор, который должен использовать значение, которое ему было предоставлено.С фьючерсами 0.1, Future::poll заняло self: &mut Self, что фактически означало, что мой комбинатор содержал Option, и я вызвал Option::take, когда базовое будущее разрешится.

Future::poll метод в стандартной библиотеке занимает self: Pin<&mut Self> вместо этого, поэтому я читал о гарантиях, необходимых для безопасного использования Pin.

Из *Документация модуля 1016 * на гарантию Drop (выделено мной):

Конкретно, для закрепленных данных вы должны поддерживать инвариант, что его память не будет аннулирована изв тот момент, когда он будет закреплен до тех пор, пока не вызывается drop.Память может быть аннулирована путем освобождения, но также с помощью замены Some(v) на None или вызова Vec::set_len для «уничтожения» некоторых элементов из вектора.

И Проекции и структурное закрепление (выделено):

Запрещается предлагать какие-либо другие операции, которые могут привести к удалению данных из полей при закреплении вашего типа.Например, если оболочка содержит Option<T> и есть операция take-like с типом fn(Pin<&mut Wrapper<T>>) -> Option<T>, эту операцию можно использовать для перемещения T из закрепленного Wrapper<T> -- это означает, что закрепление не может быть структурным.

Однако существующий комбинатор Map вызывает Option::take для значения элемента, когда базовое будущее разрешено:

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
    match self.as_mut().future().poll(cx) {
        Poll::Pending => Poll::Pending,
        Poll::Ready(output) => {
            let f = self.f().take()
                .expect("Map must not be polled after it returned `Poll::Ready`");
            Poll::Ready(f(output))
        }
    }
}

Метод f генерируется макросом unsafe_unpinned и выглядит примерно так:

fn f<'a>(self: Pin<&'a mut Self>) -> &'a mut Option<F> {
    unsafe { &mut Pin::get_unchecked_mut(self).f }
}

Это выглядит , что Mapнарушает требования, описанные в документации pin, но я считаю, что авторы комбинатора Map знают, что они делают, и что этот код безопасен.

Какая логика позволяет им выполнять этобезопасная работа?

Ответы [ 2 ]

6 голосов
/ 13 мая 2019

Это все о структурном закреплении.

Во-первых, я буду использовать синтаксис P<T>, чтобы обозначать что-то вроде impl Deref<Target = T> - некоторый (умный) тип указателя P, который Deref::deref сT.Pin только «применимо» к / имеет смысл для таких (умных) указателей.

Скажем, у нас есть:

struct Wrapper<Field> {
    field: Field,
}

Исходный вопрос

Можем ли мы получить Pin<P<Field>> от Pin<P<Wrapper<Field>>>, «проецируя» нашу Pin<P<_>> с Wrapper на field?

Для этого требуется базовая проекция P<Wrapper<Field>> -> P<Field>, что возможно только для:

  • общих ссылок (P<T> = &T).Это не очень интересный случай, учитывая, что Pin<P<T>> всегда deref с T.

  • уникальные ссылки (P<T> = &mut T).

Я буду использовать синтаксис &[mut] T для этого типа проекции.

Теперь возникает вопрос:

Можем ли мы перейти от Pin<&[mut] Wrapper<Field>> to Pin<&[mut] Field>?

Вопрос, который может быть неясен из документации, заключается в том, что решение за создателем Wrapper должен решать

Возможны два вариантадля автора библиотеки для каждого поля структуры.

Существует структурная проекция Pin на это поле

Например, макрос pin_utils::unsafe_pinned! используется для определениятакая проекция (Pin<&mut Wrapper<Field>> -> Pin<&mut Field>).

Чтобы проекция Pin была звуковой:

  • вся структура должна реализовывать Unpin только тогда, когда все полядля которого имеется структурное Pin проекционное приспособление Unpin.

    • ; ни одна реализация не может использовать unsafe для перемещения таких полей из Pin<&mut Wrapper<Field>> (или Pin<&mut Self> при Self = Wrapper<Field>).Например, Option::take() запрещено .
  • вся структура может реализовать Drop, только если Drop::drop не перемещает ни одно из полейдля которого есть структурная проекция.

  • структура не может быть #[repr(packed)] (следствие предыдущего пункта).

В вашемприведенный пример future::Map, это случай поля future структуры Map.

Нет структурной проекции Pin на это поле

Например, макрос pin_utils::unsafe_unpinned! используется для определения такой проекции (Pin<&mut Wrapper<Field>> -> &mut Field).

В этом случае это поле не считаетсяPin<&mut Wrapper<Field>>.

  • независимо от того, Field равно Unpin или нет, не имеет значения.

    • реализациям разрешено использовать unsafeпереместить такие поля из Pin<&mut Wrapper<Field>>.Например, Option::take() разрешено .
  • Drop::drop также разрешено перемещать такие поля,

В приведенном вами примере future::Map это случай поля f структуры Map.

Пример обоих типов проекции

impl<Fut, F> Map<Fut, F> {
    unsafe_pinned!(future: Fut); // pin projection -----+
    unsafe_unpinned!(f: Option<F>); // not pinned --+   |
//                                                  |   |
//                 ...                              |   |
//                                                  |   |
    fn poll (mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
        //                                          |   |
        match self.as_mut().future().poll(cx) { // <----+ required here
            Poll::Pending => Poll::Pending, //      |
            Poll::Ready(output) => { //             |
                let f = self.f().take() // <--------+ allows this
5 голосов
/ 11 мая 2019

edit : Этот ответ неверный.Это останется здесь для потомков.

Давайте начнем с напоминания, почему Pin был введен в первую очередь: мы хотим статически гарантировать, что фьючерсы с собственной ссылкой не могут быть перемещены, тем самым аннулируя их внутренние ссылки.

Имея это в виду, давайте посмотрим на определение Map.

pub struct Map<Fut, F> {
    future: Fut,
    f: Option<F>,
}

Map имеет два поля, первое хранит будущее, второе хранит закрытие, которое отображаетрезультат этого будущего в другую ценность.Мы хотим поддерживать хранение самоссылочных типов непосредственно в future, не помещая их за указателем.Это означает, что если Fut является самоссылочным типом, Map не может быть перемещен после его создания.Вот почему мы должны использовать Pin<&mut Map> в качестве приемника для Future::poll.Если обычная изменяемая ссылка на Map, содержащая самоссылочное будущее, когда-либо предоставлялась разработчиком Future, пользователи могли бы заставить UB использовать только безопасный код, вызывая перемещение Map с помощью mem::replace.

Однако нам не нужно поддерживать хранение самоссылающихся типов в f.Если мы предположим, что самоссылочная часть Map целиком содержится в future, мы можем свободно изменять f, если мы не разрешаем перемещать future.

Хотя самоссылочное закрытие будет очень необычным, предположение о том, что f безопасно для перемещения (что эквивалентно F: Unpin), нигде явно не указано.Однако мы по-прежнему перемещаем значение в f в Future::poll, вызывая take!Я думаю, что это действительно ошибка, но я не уверен на 100%.Я думаю, что для f() геттера должно потребоваться F: Unpin, что означает, что Map может реализовать Future только тогда, когда аргумент закрытия безопасен для перемещения из-за Pin.

Очень возможно, чтоЯ пропускаю некоторые тонкости в пин-API здесь, и реализация действительно безопасна.Я все еще оборачиваюсь вокруг этого.

...