Rust: Проблема отслеживания для API контактов (RFC 2349)

Созданный на 18 мар. 2018  ·  211Комментарии  ·  Источник: rust-lang/rust

Проблема с отслеживанием для rust-lang / rfcs # 2349

Блокировка стабилизации:

  • [x] Реализация (PR # 49058)
  • [ ] Документация

Нерешенные вопросы:

  • [] Должны ли мы предоставить более строгие гарантии на случай утечки данных !Unpin ?

Изменить : Сводный комментарий: https://github.com/rust-lang/rust/issues/49150#issuecomment -409939585 (в части, скрытой по умолчанию)

B-RFC-approved C-tracking-issue T-lang T-libs

Самый полезный комментарий

@rfcbot проблема api-refactor

Немного вдохновляющей структуры, и вчера вечером я понял, как мы можем реорганизовать этот API, чтобы был только один тип Pin , который обертывает указатель, вместо того, чтобы создавать закрепленную версию каждого отдельного указателя. Это никоим образом не является фундаментальным изменением API, но кажется, что лучше вытащить компонент «фиксирует память» в составную часть.

Все 211 Комментарий

Теперь я замечаю, что закрепление стека не является частью RFC, @withoutboats , вы планируете выпустить ящик для этого, или я должен просто скопировать пример кода в свой ящик, который в нем нуждается?

@ Nemo157 Вы должны скопировать это и сообщить о своем опыте!

К этому относится нерешенный вопрос об утечке данных Unpin . Этот API не работает, если мы говорим, что вы не можете перезаписать данные Unpin в Pin если деструктор не запустится, как запросил @cramertj . Существуют другие, менее эргономичные API-интерфейсы закрепления стека, которые действительно работают для этого. Неясно, какой здесь правильный выбор - более полезен эргономичный API закрепления стека или дополнительная гарантия утечки более полезна?

Замечу одну вещь: закрепления стека было недостаточно для таких вещей, как Future::poll внутри макроса await! , потому что это не позволяло нам проводить опрос в цикле. Мне было бы интересно, столкнетесь ли вы с этими проблемами, и как / если вы их решите, если вы это сделаете.

Мое текущее использование довольно тривиально, однопоточный исполнитель, запускающий один StableFuture без поддержки порождения . Переход на такой API, как предлагает @cramertj , с этим отлично StableFuture s, но, по крайней мере, с моим текущим проектом в этом нет необходимости.

Просто попробовал поэкспериментировать с API. Похоже, следующее (предложенное RFC) определение Future больше не является объектно-безопасным?

trait Future {
    type Item;
    type Error;

    fn poll(self: Pin<Self>, cx: &mut task::Context) -> Poll<Self::Item, Self::Error>;
}

Ничего. Нашел заметку о плане сделать arbitrary_self_types объектно-безопасным.

@withoutboats

Замечу одну вещь: закрепления стека было недостаточно для таких вещей, как Future :: poll внутри await! макрос, потому что он не позволял нам проводить опрос в цикле.

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

@RalfJung Вам нужно Pin для поддержки повторных заимствований как Pin , чего в настоящее время нет.

@cramertj Это похоже на ограничение API Pin , а не API закрепления стека?

@RalfJung Да, это правильно. Однако PinBox можно повторно заимствовать как Pin , в то время как тип с закреплением стека не может (одно заимствование как Pin создает заимствование на все время существования типа стека).

Учитывая Pin , я могу одолжить его как &mut Pin а затем использовать Pin::borrow - это форма повторного заимствования. Я так понимаю, вы говорите не о том, о чем вы говорите?

@RalfJung Нет - методы вроде Future::poll планировалось использовать self: Pin<Self> , а не self: &mut Pin<Self> (что не является допустимым типом self так как это не t Deref<item = Self> - это Deref<Item = Pin<Self>> ).

Возможно, мы могли бы заставить это работать с Pin::borrow самом деле. Я не уверен.

@cramertj Я не предлагал вызывать poll на x: &mut Pin<Self> ; Я подумал о x.borrow().poll() .

@RalfJung О, понятно. Да, использование метода повторного заимствования вручную может сработать.

Я постараюсь не забыть опубликовать на следующей неделе пример некоторых вещей, которые я делаю с Pin s, насколько я могу судить, перезимование работает отлично. У меня есть закрепленная версия трейта futures::io::AsyncRead вместе с рабочими адаптерами, такими как fn read_exact<'a, 'b, R: PinRead + 'a>(read: Pin<'a, R>, buf: &'b [u8]) -> impl StableFuture + 'a + 'b и я могу преобразовать это в относительно сложный StableFuture который просто закреплен наверху уровень.

Вот полный пример того, что я использую для чтения:

pub trait Read {
    type Error;

    fn poll_read(
        self: Pin<Self>,
        cx: &mut task::Context,
        buf: &mut [u8],
    ) -> Poll<usize, Self::Error>;
}

pub fn read_exact<'a, 'b: 'a, R: Read + 'a>(
    mut this: Pin<'a, R>,
    buf: &'b mut [u8],
) -> impl StableFuture<Item = (), Error = Error<R::Error>>
         + Captures<'a>
         + Captures<'b> {
    async_block_pinned! {
        let mut position = 0;
        while position < buf.len() {
            let amount = await!(poll_fn(|cx| {
                Pin::borrow(&mut this).poll_read(cx, &mut buf[position..])
            }))?;
            position += amount;
            if amount == 0 {
                Err(Error::UnexpectedEof)?;
            }
        }
        Ok(())
    }
}

Это немного раздражает, поскольку вы должны передавать экземпляры везде как Pin и использовать Pin::borrow всякий раз, когда вы вызываете на них функции.

#[async]
fn foo<'a, R>(source: Pin<'a, R>) -> Result<!, Error> where R: Read + 'a {
    loop {
        let mut buffer = [0; 8];
        await!(read_exact(Pin::borrow(&mut source), &mut buffer[..]));
        // do something with buffer
    }
}

У меня просто возникла мысль, что я могу impl<'a, R> Read for Pin<'a R> where R: Read + 'a обходиться без необходимости передавать значения как Pin<'a, R> везде, вместо этого я мог бы использовать fn foo<R>(source: R) where R: Read + Unpin . К сожалению, это не удается, потому что Pin<'a, R>: !Unpin , я думаю, безопасно добавить unsafe impl<'a, T> Unpin for Pin<'a, T> {} поскольку сам вывод является всего лишь ссылкой, а данные за ним все еще закреплены.

Обеспокоенность: кажется вероятным, что мы хотим, чтобы большинство типов в libstd безоговорочно реализовывало Unpin , даже если их параметры типа не равны Pin . Примеры: Vec , VecDeque , Box , Cell , RefCell , Mutex , RwLock , Rc , Arc . Я ожидаю, что большинство ящиков вообще не будут думать о закреплении, и, следовательно, их типы будут Unpin случае, если все их поля равны Unpin . Это разумный выбор, но он приводит к излишне слабым интерфейсам.

Решит ли это само себя, если мы реализуем Unpin для всех типов указателей libstd (возможно, даже включая необработанные указатели) и UnsafeCell ? Мы захотим этим заняться?

Решит ли это само себя, если мы обязательно реализуем Unpin для всех типов указателей libstd (возможно, даже включая необработанные указатели) и UnsafeCell? Мы захотим этим заняться?

Да, мне кажется, что это та же ситуация, что и Send .

Только что у меня возник новый вопрос: когда Pin и PinBox Send ? Прямо сейчас механизм автоматической характеристики делает их Send всякий раз, когда T равно Send . Для этого нет априорной причины; точно так же, как типы в общем типе имеют свой собственный маркер для возможности отправки (называемый Sync ), мы могли бы сделать маркер, говорящий, когда Pin<T> равно Send , например PinSend . В принципе, можно писать типы Send но не PinSend и наоборот.

@RalfJung Pin - это Отправить, когда &mut T - Отправить. PinBox - Отправить, если Box<T> - Отправить. Я не вижу причин для их отличия.

Ну, точно так же, как некоторые типы Send но не Sync , у вас может быть тип, основанный на "Как только этот метод вызывается с Pin<Self> , я могу рассчитывать, что меня никогда не переместят в другую ветку ". Например, это может привести к появлению фьючерсов, которые можно отправлять перед первым запуском, но затем они должны оставаться в одном потоке (так же, как их можно перемещать перед запуском, но затем они должны оставаться закрепленными). Не уверен, что смогу привести убедительный пример, может быть, что-нибудь о будущем, в котором используется локальное хранилище потоков?

Я только что столкнулся с проблемой жизни, упомянутой @Diggsey . Я считаю, что Pin<Option<T>> -> Option<Pin<T>> должна быть безопасной операцией, но не представляется возможным реализовать даже с использованием текущих небезопасных API, не говоря уже о том, какой API потребуется для создания этого безопасного кода:

trait OptionAsPin<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>>;
}

impl<T> OptionAsPin<T> for Option<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>> {
        match *unsafe { Pin::get_mut(&mut self) } {
            Some(ref mut item) => Some(unsafe { Pin::new_unchecked(item) }),
            None => None,
        }
    }
}

(Можно обойти проблему, используя трансмутацию, чтобы заставить время жизни, но это заставляет меня чувствовать себя слишком неприятно).

Я хотел бы добавить нерешенный вопрос: следует ли нам снова добавить общий закрепленный ссылочный тип? Думаю, да. См. Этот пост с обсуждением для получения более подробной информации.

Я только что прочитал, что фьючерс 0.2 не такой окончательный, как я думал , так что, возможно, все же возможно переименовать Pin обратно в PinMut и добавить общую версию.

@RalfJung Я еще раз более

Я думаю, вы нашли потенциально убедительный вариант использования неизменяемого варианта Pin, но мне не понятны ваши комментарии о Deref и &Pin<T> <=> &&T . Даже если Pin<T> можно безопасно преобразовать в &T , это не делает их эквивалентными, потому что &T нельзя преобразовать в Pin<T> . Я не вижу причин делать безопасное преобразование небезопасным (удаляя безопасный Deref impl).

В настоящее время метод map для Pin имеет подпись

pub unsafe fn map<U, F>(this: &'b mut Pin<'a, T>, f: F) -> Pin<'b, U>

По какой причине этого не происходит?

pub unsafe fn map<U, F>(this: Pin<'a, T>, f: F) -> Pin<'a, U>

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

Другая проблема с методом map заключается в том, что кажется невозможным превратить Pin структуры в два Pin s, каждое из которых имеет разные поля структуры. Как правильно этого добиться?

Я использовал для этого этот макрос:

macro_rules! pin_fields {
    ($pin:expr, ($($field:ident),+ $(,)?)) => {
        unsafe {
            let s = Pin::get_mut(&mut $pin);
            ($(Pin::new_unchecked(&mut s.$field),)+)
        }
    };
}

Во многих дискуссиях вокруг Pin кажется, есть предположение, что "проецирование" булавки на частное поле должно считаться безопасным. Не думаю, что это совсем правда. Например, комментарий документа к map настоящее время гласит:

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

Гарантия того, что «возвращаемые вами данные не будут перемещаться, пока не перемещается значение аргумента» - это правильное описание контракта, который вызывающий map должен соблюдать. Заключение в скобки «(например, потому что это одно из полей этого значения)», похоже, подразумевает, что пока вы просто возвращаете ссылку на свое личное поле, вы гарантированно в безопасности. Но это не так, если вы реализуете Drop . Drop impl будет видеть &mut self , даже если другой код уже видел Pin<Self> . Он может перейти к использованию mem::replace или mem::swap для выхода за пределы своих полей, нарушая обещание, данное ранее "правильным" использованием map .

Другими словами: используя вызов «правильной проекции вывода» для Pin::map (вызов выглядит как unsafe { Pin::map(&mut self, |x| &mut x.p) } ), без других вызовов unsafe , можно получить unsound / undefined поведение. Вот ссылка на игровую площадку, демонстрирующая это.

Это не означает, что с текущим API что-то не так. Это демонстрирует, что Pin::map следует пометить как unsafe , что уже и есть. И я не думаю , что есть большая опасность людей , случайно споткнувшись это при осуществлении фьючерсных или тому подобное - вы действительно должны выйти из своего пути , чтобы выйти из ваших собственных полей в Drop осущ и Я сомневаюсь, что для этого есть много практических причин.

Но я действительно думаю, что комментарий документа для map может захотеть упомянуть об этом, и я также думаю, что он лишает законной силы некоторые идеи для расширений, которые я вижу в RFC и окружающих обсуждениях:

  • Там не должно быть макро / получаем , что делает «контактный выступ» и делает вид безопасным. Проекция действительно накладывает контракт на другой окружающий его код, который компилятор не может полностью обеспечить. Таким образом, он должен требовать использования ключевого слова unsafe всякий раз, когда это делается.
  • В RFC упоминается, что если Pin превратить в языковую функцию &'a pin T , было бы «тривиально проецировать через поля». Я считаю, что показал, что для этого все равно потребуется unsafe , даже если оно ограничено проектированием частных полей.

@withoutboats

Даже если пинможно безопасно преобразовать в & T, что не делает их эквивалентными, потому что & T нельзя преобразовать в Pin.

В самом деле, этого недостаточно. В моей модели они равны, потому что в моем предыдущем посте мы сделали следующее определение:

Определение 5: PinBox<T>.shr . Указатель ptr и время жизни 'a удовлетворяют общему типу PinBox<T> если ptr является указателем только для чтения на другой указатель inner_ptr, такой что T.shr('a, inner_ptr)

(И, как бы неявно, соответствующее определение для Pin<'a, T>.shr .)
Обратите внимание, как PinBox<T>.shr зависит от T.shr и ничего больше. Это делает PinBox<T>.shr точно таким же инвариантом, как Box<T>.shr , что означает, что &Box<T> = &PinBox<T> . Аналогичные рассуждения показывают, что &&T = &Pin<T> .

Таким образом, это не следствие API или контракта, записанного в RFC. Это следствие того, что модель имеет всего три состояния типа: Owned, shared, pinned. Если вы хотите возразить против &&T = &Pin<T> , вы должны привести доводы в пользу введения четвертого типа-состояния, «общий закрепленный».

@MicahChalmer

Во многих дискуссиях вокруг Pin, кажется, есть предположение, что «проецирование» булавки на частное поле должно рассматриваться как безопасное. Не думаю, что это совсем правда.

Это очень хороший момент! Чтобы прояснить, нет никаких проблем с тем, чтобы сделать поле p в Shenanigans общедоступным, не так ли? В этот момент любой клиент может написать do_something_that_needs_pinning , и цель RFC - сделать это безопасным. (Я не знаю, почему RFC специально упоминает частные поля, я всегда считал, что он должен работать со всеми полями.)

Интересно посмотреть, как это взаимодействует с интерпретацией drop в моей модели . Там я написал, что drop(ptr) имеет предусловие T.pin(ptr) . При такой интерпретации фактическим кодом в вашем примере будет реализация drop ! (Теперь мне интересно, почему я не заметил этого уже при написании сообщения ...) Я думаю, что мы действительно хотим разрешить безопасные проекции для полей в конечном итоге, и нам действительно не следует предоставлять drop с &mut если тип закрепляется. Это явно подделка.

Есть ли способ: (а) сделать impl Drop небезопасным, если тип !Unpin , или (б) изменить его подпись на drop(self: Pin<Self>) если T: !Unpin ? Оба звучат крайне надуманно, но, с другой стороны, оба решат точку @MicahChalmer , сохранив при этом безопасность проекций поля. (Если бы только это было до версии 1.0, и мы могли бы изменить Drop::drop чтобы всегда принимать Pin<Self> ;) Но, конечно, на данный момент это решение больше не только для библиотеки. Печально то, что если мы стабилизируемся как есть, у нас никогда не будет безопасных проекций поля.

@RalfJung Так что меня больше интересуют практические вопросы (как и следовало ожидать: wink :). Думаю, их два:

  • Следует ли Pin<T: !Unpin> реализовать Deref<Target =T> ?
  • Должны ли быть одновременно и Pin<T> и PinMut<T> (первый - общий контакт)?

Не вижу причин отрицательно отвечать на первый вопрос. Я думаю , что вы вновь открыли второй вопрос; Я склонен вернуться к двум разным типам контактов (но не совсем уверен). Если мы когда-нибудь обновим его до ссылочного типа на уровне языка, у нас будет &pin T и &pin mut T .

Кажется, что независимо от того, что мы делаем, вашей модели, вероятно, потребуется четвертый тип-состояние, чтобы точно отражать инварианты API. Я не думаю, что преобразование &&T в &Pin<T> должно быть безопасным.

Так что меня больше интересуют практические вопросы (как и следовало ожидать).

Справедливо. ;) Тем не менее, я думаю, что важно иметь хотя бы базовое представление о том, что является, а что не гарантируется около Pin только небезопасный код появляется. Чтобы понять это, в Basic Rust потребовалось несколько лет стабилизации, теперь мы повторяем это для Pin через несколько месяцев. В то время как Pin является добавлением только для библиотеки с точки зрения синтаксиса, это значительное дополнение с точки зрения модели. Я думаю, что было бы разумно задокументировать как можно более подробно, что такое небезопасный код и что не разрешено принимать или делать около Pin .

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


По поводу вашего второго вопроса:

Кажется, что независимо от того, что мы делаем, вашей модели, вероятно, потребуется четвертый тип-состояние, чтобы точно отражать инварианты API. Я не думаю, что преобразование && T в & Pinдолжно быть безопасно.

Я этого и ожидал.

Если мы хотим быть консервативными, мы могли бы переименовать Pin в PinMut но не добавлять PinShr (вероятно, будет называться Pin но я пытаюсь устранить неоднозначность здесь ) и заявляем, что небезопасный код может предполагать, что &PinMut<T> самом деле указывает на что-то закрепленное. Затем у нас будет возможность добавить Pin в качестве общей закрепленной ссылки позже.

Практическая причина наличия PinShr была предложена @comex : должна быть возможность предоставить геттер, который идет от PinShr<'a, Struct> до PinShr<'a, Field> ; если мы используем &PinMut для общих контактов, это не работает. Не знаю, сколько понадобится таких геттеров.


На ваш первый вопрос, похоже, есть несколько веских аргументов в пользу: (а) текущие планы для объектно-безопасных произвольных типов self и (б) возможность безопасно использовать огромное количество существующих API-интерфейсов для общих ссылок при удерживании PinShr (или PinMut ).

К сожалению, у нас, похоже, нет простого способа аналогичным образом предоставить операции, которые могут работать как с &mut и с PinMut ; в конце концов, много кода, работающего с &mut не собирается использовать mem::swap . (На мой взгляд, это будет главным преимуществом решения на основе !DynSized или чего-то сопоставимого: &mut превратится в тип для ссылок, которые могут или не могут быть закреплены. Мы мог бы иметь это как еще один тип библиотеки в Pin API, но это довольно бессмысленно, учитывая, что у нас уже есть все эти &mut методы.)

Есть один легкий аргумент против: типы, которые хотят делать "интересные" вещи как для &T и для PinMut<T> . Это будет сложно сделать, не все возможно, и нужно быть очень осторожным. Но я не думаю, что это перевешивает веские аргументы в пользу.

В этом случае (то есть с этим impl Deref ) PinShr<'a, T> должен иметь безопасный метод, который превращает его в &'a T (сохраняя время жизни).


И есть еще одна проблема, которую, я думаю, мы должны решить: правила для Pin::map и / или drop в свете того, что @MicahChalmer заметил выше. У нас есть два варианта:

  • Объявите, что использование Pin::map с проекцией на публичное поле (без derefs, даже неявных) всегда безопасно. Это моя интерпретация текущего RFC. Это будет соответствовать &mut и & . Однако тогда у нас есть проблема с Drop : теперь мы можем написать безопасный код, который заставляет UB задавать в остальном правильно сформированный тип !Unpin .
  • Не делай ничего смешного с Drop . Затем мы должны добавить громкие предупреждения к Pin::map что даже его использование для общедоступных полей может привести к несостоятельности. Даже если у нас когда-либо будет &pin T , мы не сможем использовать это для доступа к полю в безопасном коде.

Единственный возможный аргумент в пользу второго варианта, который я вижу, заключается в том, что он может быть единственным, который мы действительно можем реализовать. ;) Я считаю, что он уступает во всех смыслах - он делает &pin довольно странным и неэргономичным, даже если в один прекрасный день он становится встроенным, это ножное ружье, мешает композиционности.

Возможно, есть способ реализовать первый вариант, но это нетривиально, и я понятия не имею, как сделать его обратно совместимым: мы могли бы добавить Unpin привязанный к Drop , и добавить DropPinned , который не имеет границы и где drop принимает Pin<Self> . Может быть, Unpin связанное с Drop могло быть применено странным образом, когда вы могли написать impl Drop for S , но это добавляет неявную привязку к S говоря, что это должно быть Unpin . Наверное, нереально. : / (Я думаю, это также та точка, где подход на основе !DynSized работает лучше - он превращает &mut T в "может или не может быть закреплен", сохраняя drop звук.)

@RalfJung @MicahChalmer Я думаю, что лучше просто задокументировать, что если вы выйдете из поля в Drop impl, проецирование на Pin этого поля в другом месте будет неадекватным.

В самом деле, сегодня уже случается, что (используя небезопасный код) вы можете выйти из поля типа !Unpin , и это безопасно и хорошо определено, если вы никогда не проецируете на вывод этого поля в другом месте. . Единственная разница с Drop состоит в том, что выдвижная часть содержит только безопасный код. Мне кажется , что ноты на Pin::map нужно изменить , чтобы отметить , что это не безопасно , если вы когда - либо выйти из этой области, независимо от осущ падения.

В некоторых случаях должно быть безопасно выходить из полей типа !Unpin , потому что генераторы, скорее всего, выйдут из одного из своих полей, когда они вернутся.

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

Это второй вариант, который делает операцию &pin над полем постоянно небезопасной.
Думаю, это немалое изменение. Это коренным образом меняет значение pub в поле. При использовании библиотечного типа я не могу знать, что он делает в своем drop impl, поэтому у меня принципиально нет способа получить закрепленную ссылку на это поле.

Например, мне бы даже не разрешили перейти от Pin<Option<T>> к Option<Pin<T>> если Option явно не заявит, что Drop никогда ничего не сделает " смешной". Компилятор не может понять этот оператор, поэтому, хотя Option может предоставить соответствующий метод для этого, выполнение того же самого с match должно оставаться небезопасной операцией.

Единственная разница с Drop заключается в том, что выдвижная часть содержит только безопасный код.

Но ведь это огромная разница, не правда ли? Мы можем установить произвольные правила в отношении того, что небезопасный код может или не может делать, но не в отношении безопасного кода.

В некоторых случаях должно быть безопасно выходить из полей типа! Unpin, потому что генераторы, скорее всего, выйдут из одного из своих полей, когда они вернутся.

Полагаю, в этом случае поле будет Unpin ? Таким образом, мы могли бы иметь правило, согласно которому Pin::mut для открытого поля внешней структуры нормально, если это поле имеет тип Unpin . Не уверен, насколько это полезно, но, вероятно, это лучше, чем ничего.

Я хочу быстро выразить свое недоумение по поводу того, что &Pin<T> не дает больше гарантий, чем &&T . & , &mut и &pin соответственно предоставляют «общий доступ», «уникальный доступ» и «уникальный доступ к значению, которое не будет перемещено». Понимание &&pin как «общий доступ к уникальному доступу к типу, который не будет перемещен» говорит вам о том, что память является общей (гарантия уникальности &pin отменяется совместным использованием & ), но вы по-прежнему сохраняете свойство, что тип не будет перемещен, не так ли?

Я не совсем понимаю, о чем вы спрашиваете или говорите. Вы не понимаете, почему я думаю, что «shared pinned» - это фундаментальный режим / типовое состояние само по себе?

Дело в том, что «общий доступ» - это не то, что я знаю, как определять отдельно. Существует много разных способов совместного использования и координации совместного использования, о чем свидетельствуют очень разные способы, например, Cell , RefCell и Mutex share.

Вы не можете просто сказать, что делитесь чем-то («отменяя гарантию уникальности» чего-либо), которым владеете, и ожидать, что это утверждение будет иметь смысл. Вы должны сказать, как вы делитесь информацией и как гарантировать, что это не вызовет хаоса. Вы можете «поделиться, сделав его доступным только для чтения», или «поделиться, предоставив атомарный доступ только через синхронизированные загрузки / хранилища», или «поделиться [в пределах одного потока], указав этот флаг заимствования, координируя, какой вид доступа передавать» . Одним из ключевых моментов в RustBelt было осознание важности того, чтобы каждый тип мог сам определять, что происходит, когда он становится общедоступным.

Я не могу придумать способ сделать так, чтобы «совместное закрепление» возникло как ортогональная композиция совместного использования и закрепления. Может быть, есть способ определить понятие «механизма совместного использования», которое затем можно было бы применить либо к собственному, либо к закрепленному инварианту, чтобы создать «(нормальный) общий» и «закрепленный общий», но я серьезно в этом сомневаюсь. Кроме того, как мы видели, это не подходит для RefCell - если RefCell делает для совместно используемого закрепленного инварианта нечто подобное тому, что он делает для только что совместно используемого инварианта, мы определенно не можем оправдать это с помощью & &pin RefCell<T> через &RefCell<T> (используя Deref ) через borrow_mut мы можем получить ссылку &mut которой говорится, что закрепления не произошло.

@RalfJung

Мы могли бы добавить Unpin, привязанный к Drop, и добавить DropPinned, у которого нет привязки и где drop принимает Pin.

Действительно ли здесь проблема с определением Drop ? Другой способ подумать об этом - возложить вину на mem::swap и mem::replace . Это операции, которые позволяют вам перемещать то, что вам не принадлежит. Предположим, что к _them_? Были добавлены привязки T: Unpin

Для начала, это исправит дыру в drop которую я указал - мой Shenanigans не смог бы скомпилироваться, и я не думаю, что смогу заставить его нарушить обещания булавки без еще одного unsafe . Но это позволило бы больше! Если стало безопасно получить ссылку &mut на ранее закрепленное значение в drop , зачем ограничивать ее только drop ?

Если я чего-то не упускаю, я думаю, что это сделает безопасным заимствование ссылки &mut из PinMut<T> любое время. (Я использую PinMut для обозначения того, что в настоящее время называется Pin , чтобы избежать путаницы с обсуждением общих контактов.) PinMut<T> может реализовать DerefMut безусловно, а не только для T: Unpin .

К сожалению, у нас, похоже, нет простого способа аналогичным образом предоставить операции, которые могут работать как с & mut, так и с PinMut; в конце концов, много кода, работающего над & mut, не собирается использовать mem::swap .

DerefMut impl на PinMut исправит это, верно? Код, который заботится о закреплении и требует PinMut , может безопасно и легко вызывать код, который работает с &mut . Нагрузка будет помещен вместо этого на общих функций , которые _do_ хотите использовать mem::swap --those придется иметь Unpin Bound добавил к ним, или использовать unsafe и заботиться не нарушать условия вывода.

Добавление таких границ к swap и replace сейчас нарушило бы обратную совместимость, начиная с самого первого стабильного выпуска. Я не вижу реального способа добраться отсюда. Но упускаю ли я какую-то другую дыру в мысли, что это было бы то, что нужно было бы сделать, если бы это было известно в дни до 1.0?

Как бы то ни было, я не вижу лучшего решения, чем то, что сказал @withoutboats - оставьте map небезопасным и поместите в его документы сообщение, предупреждающее людей не выходить из любого поля, которое ранее было закреплено в drop имп.

Мы можем установить произвольные правила в отношении того, что небезопасный код может или не может делать, но не в отношении безопасного кода.

Использование unsafe всегда накладывает правила на окружающий безопасный код. Хорошая новость заключается в том, что, насколько нам известно, если закрепляемая структура имеет метод для проецирования булавки в частное поле, только ее собственный drop impl может использовать это для нарушения контракта в безопасном коде. Таким образом, все еще возможно добавить такую ​​проекцию и предоставить _users_ этой структуры полностью безопасный API.

Действительно ли здесь проблема с определением Drop?

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

Другой способ подумать об этом - возложить вину на mem :: swap и mem :: replace. Это операции, которые позволяют вам перемещать то, что вам не принадлежит. Предположим, к ним добавлена ​​граница T: Unpin?

Что ж, это сделало бы &mut T целом безопасным для использования с типами !Unpin . Как вы заметили, нам больше не потребуется PinMut . PinMut<'a, T> в вашем предложении можно определить как &'a mut T , верно?
По сути, это предложение ?Move , от которого ранее отказались из-за проблем с обратной совместимостью и языковой сложности.

Использование unsafe всегда накладывает правила на окружающий безопасный код.

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

Хорошая новость заключается в том, что, насколько нам известно, если закрепляемая структура имеет метод для проецирования булавки в частное поле, только ее собственный drop impl может использовать это для нарушения контракта в безопасном коде. Таким образом, все еще возможно добавить такую ​​проекцию и предоставить пользователям этой структуры полностью безопасный API.

Да, типы могут отказаться от объявления проекции безопасной. Но, например, программа проверки заимствований не поймет, что это доступ к полю, поэтому, учитывая PinMut<Struct> вы не можете использовать такие методы для получения PinMut в двух разных полях.

Но есть ли у нас согласие, что изменение Drop, как я предлагал, решит проблему?

Я согласен, это исправит.

Нам даже не понадобится PinMut. PinMut <'a, T> в вашем предложении можно определить как &' a mut T, верно?

Нет, PinMut<'a, T> по-прежнему требуется, чтобы обещать, что референт больше никогда не переместится. С &'a mut T можно было только доверять ему не двигаться на всю жизнь 'a . Это все еще будет разрешено, как и сегодня:

ржавчина
struct X;
impl! Открепить для X {}
fn take_a_mut_ref (_: & mut X) {}

fn заимствовать_and_move_and_borrow_again () {
пусть mut x = X;
принимает_a_mut_ref (& mut x);
пусть mut b = Box :: new (x);
принимает_a_mut_ref (& mut * b);
}
`` ''

Было бы безопасно перейти от PinMut<'a, T> к &'a mut T но не наоборот - PinMut::new_unchecked все равно будет существовать, и по-прежнему будет unsafe .

По сути, это предложение ?Move , от которого ранее отказались из-за проблем с обратной совместимостью и языковой сложности.

Насколько я понимаю, предложения ?Move пытались полностью избавиться от необходимости PinMut , изменив основные языковые правила, чтобы запретить приведенный выше фрагмент кода (сделав Unpin быть чертой Move .) Я не предлагаю ничего подобного - я предлагаю начать именно так, как сейчас в ночное время, плюс:

  • Добавьте Unpin привязанный к функциям std::mem::swap и std::mem::replace
  • Удалите границу Unpin из DerefMut impl из Pin ( PinMut если это переименование произойдет)

Вот и все - никаких фундаментальных изменений языка в работе движений или чего-то подобного. Я утверждаю: да, это было бы критическое изменение, но оно будет иметь меньшее разрушающее воздействие, чем изменение Drop (которое, в свою очередь, все еще менее радикально, чем предложения ?Move ), при сохранении многих преимуществ. В частности, это позволит безопасную проекцию булавки (по крайней мере, для частных полей, и я думаю, даже для общедоступных? Не совсем уверен) и предотвратит ситуации, когда параллельный код должен быть написан для работы с PinMut и &mut .

Нет, PinMut <'a, T> по-прежнему требуется, чтобы обещать, что референт больше никогда не переместится. С & 'a mut T можно было только доверять ему, чтобы он не двигался всю жизнь' a.

Понимаю. Имеет смысл.

Насколько я понимаю, предложения? Move пытались полностью избавиться от необходимости PinMut, изменив основные языковые правила, чтобы запретить приведенный выше фрагмент кода (сделав Unpin свойством Move).

Понятно.

Я утверждаю: да, это будет серьезное изменение, но оно будет иметь меньшее разрушающее воздействие, чем изменение Drop.

Какое изменение вы имеете в виду? Добавление привязки Unpin ? Возможно, вы правы, но я понятия не имею, насколько широко используются mem::swap и mem::replace .

В частности, это позволит безопасно проецировать булавки (по крайней мере, для частных полей, и я думаю, даже для общедоступных? Не совсем уверен)

Я не понимаю, как частные и публичные могут иметь здесь значение. Как публичное поле допускает меньшее, чем частное?

Но да, в целом это, похоже, держится. Future прежнему будет принимать PinMut потому что он должен полагаться на вещи, которые никогда не перемещаются, но у него будет более широкий спектр доступных для использования методов.

Однако аспект совместимости очень велик. Я не думаю, что это реально, это сломало бы весь общий код, который вызывает mem::swap / mem::replace . Кроме того, прямо сейчас небезопасный код может реализовать эти методы самостоятельно с помощью ptr::read / ptr::write ; это может привести к тихой поломке существующего небезопасного кода. Думаю, это бесполезно.

Пока мы обсуждаем тему введения Unpin привязанных к mem::swap и mem::replace (и не заботясь о поломке). Если мы предположим, что используется маршрут «встроенный компилятор». Можно ли было бы также ввести ту же самую границу для mem::forget чтобы гарантировать запуск деструкторов для переменных, закрепленных в стеке, из-за которых thread::scoped звучит и в некоторых случаях не «накачивает штаны»?

Обратите внимание, что mem::forget на PinBox всегда разрешено. Новая предлагаемая гарантия, связанная с падением, НЕ гласит, что «вещи не протекают». В нем говорится, что "вещи не освобождаются, если сначала не вызывается drop ". Это совсем другое заявление. Эта гарантия не помогает thread::scoped .

Чтобы добавить контекст, я обычно делаю перемещение данных из структуры, которая реализует Future . Довольно часто возникает необходимость выполнить очистку, если будущее никогда не опрашивалось до завершения (сбрасывалось до завершения опроса).

Так что, портируя существующий код на Futures 0.3, я наверняка попал бы в эту фишку, даже если бы документация была добавлена ​​в map .

@carllerche функция map совершенно ясно говорит, что вы не должны использовать ее для перемещения чего-либо. Мы не можем и не хотим защищать от людей, намеренно покидающих Pin<T> , но для этого вам нужно уйти со своего пути (используя небезопасный код). Я бы не назвал это фугасом.

Итак, о какой мине вы имеете в виду?

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

Если это невозможно сделать, я думаю, что на практике большинство удобных API, использующих закрепление, должны будут использовать PinShare. Думаю, это может быть не такой уж большой недостаток, но я все еще не понимаю, каковы отношения с Unpin в этом случае. В частности: допустим, я беру ссылку с общим доступом и получаю ссылку на поле типа (на определенное время жизни). Могу ли я действительно рассчитывать на то, что он не двинется с места по окончании этой жизни? Я, вероятно, смогу, если поле !Unpin так что, может быть, все в порядке, пока Pin не может проецироваться - меня больше беспокоят перечисления. Если вы не утверждаете, что даже совместное закрепление не может быть безопасным без исправления отбрасывания - в этом случае я думаю, что исправление сбрасывания для работы с закреплением по существу должно произойти; в противном случае это становится нишевой библиотечной функцией, которую на самом деле нельзя безопасно использовать и которая (IMO) не заслуживает какого-либо почетного места в базовом языке, даже если это действительно очень полезно для Futures.

Я также должен упомянуть, что единственный практический API, который у меня есть для навязчивых коллекций до сих пор (мне все еще нужно решить проблемы), требует еще более серьезных гарантий, чем это; он должен быть в состоянии гарантировать , что падение не называется, пока есть какие -

Из любопытства: каковы аргументы против добавления Unpin привязанного к Drop ? Вы упомянули обратную совместимость с альтернативой, что вам нужно как-то автоматически связать объект, который был сброшен, но отбросить уже странные ограничения уровня системы типов, которых не существует для других черт - почему это так отличается? Это, конечно, не так элегантно, как просто заставить drop взять Pin<T> но на данный момент мы не можем внести это изменение. Является ли вопрос , что в действительности мы не знаем , что делать , если вы делаете падение вызова на тип , который имеет только Unpin реализацию, когда сам тип !Unpin ? Я предполагаю, что выброс исключения в реализации drop в этом случае может быть правильным подходом, поскольку любой, кто полагается на drop запущенный для общих типов, уже должен обрабатывать случай паники. Это означало бы, что было бы очень сложно использовать типы !Unpin на практике без группы людей, обновляющих свой код для использования нового признака Drop (так что экосистема была бы вынуждена переместить все в thew new version), но я думаю, что меня это устроит, так как это все равно сохранит надежность и не сломает код, который вообще не использует !Unpin . Кроме того, фраза «ваш код запаникует, если библиотека не обновится» действительно будет стимулировать людей двигаться дальше!

Фактически, вот предложенный мной дизайн:

Расширьте трейт Drop вторым методом, который принимает Pin, как вы предложили. Сделайте специализированную реализацию по умолчанию where T: Unpin call drop (я предполагаю, что это передаст текущие правила специализации? Но даже если это не так, Drop всегда может быть в особом регистре; плюс, Drop функции обычно создаются автоматически). Теперь у вас точно такое же поведение, которое я предложил выше, без проблем с обратной совместимостью и без попыток неловкого автоматического получения границы.

Проблема в том, что сторонним библиотекам придется обновить их, чтобы они стали практически полезными с типами !Unpin , но, как я уже сказал, это, возможно, хорошо. В любом случае, я понятия не имею, сколько реализаций drop самом деле изменяют свои поля способами, требующими &mut - большинство из тех, которые, как мне кажется, делают что-нибудь интересное, либо используют unsafe или использовать внутреннюю изменчивость, но я, вероятно, не думаю о некоторых распространенных вариантах использования.

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

(Я вижу еще один потенциальный вопрос: ManuallyDrop и объединения в целом означает , то мог бы , написал деструктор над родового типа , что drop реализация в нем не может запаниковать просто потому , состоянии работать (он также не будет позволено называть любые другие &mut функции на родовом T, для тех , которые обещаны не паниковать за Pin тип уже должен гарантировать, что его реализация drop выполняется до того, как его память будет уничтожена [в соответствии с новой семантикой], я считаю, что тип !Unpin в ManuallyDrop используется для этой цели в существующем коде не может считаться закрепленным в первую очередь, поэтому существующий код все равно должен быть правильным, если он делает это предположение; конечно, вы не сможете безопасно проецировать булавку в ManuallyDrop , поскольку это может быть безопасно только в том случае, если вы гарантируете, что его деструктор вызывается до отбрасывания. Я просто не знаю, как можно сообщить об этом случае команде Может ли это вообще использовать преимущества «повязки на глаз», поскольку кажется, что она предназначена для той же цели?]. Я не совсем уверен, что это работает семантически, но, вероятно, это работает для целей реализации Drop для существующего кода.

Что касается повязки на глаз ... Я до сих пор не уверен в точном формальном определении, которое она имеет, но если общая идея такова, что на T не вызываются какие-либо интересные общие функции, может быть, можно было бы использовать это в дальнейшем? Это означает, что если бы была реализована реализация drop_pinned для типа !Unpin , контейнер, соответствующий глазной повязке, работал бы правильно. Мне кажется возможным , что затем можно было бы скомпилировать сбой времени для типов !Unpin которые реализуют Drop , не реализуют drop_pinned и не отслеживают их общие параметры в так же, как и мы, когда вы используете контейнеры без повязок с саморегулируемым сроком службы.

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

Изменить: На самом деле, поцарапайте все вышеперечисленное: нам действительно нужно беспокоиться только о доступных полях в деструкторах, а не о доступе к &mut целом, правильно? Потому что нас беспокоят только неявные проекции поля из &mut self .

Возможно, я просто заново изобретаю предложение !DynSized , но по сути: общие контейнеры уже сами должны быть !Unpin если они предоставляют какие-либо методы, которые заботятся о типе вывода (я понимаю, что это подозрительно похоже на неправильную параметричность аргумент, но выслушайте меня!). Такие экзистенциалы, как Box<Trait> и &mut Trait , не обязательно отражают их статус Unpin , но (по крайней мере, глядя на текущие API?) Я не думаю, что Pin<Box<T>> и Pin<&mut T> обязательно должны быть приводимыми к Pin<T> где T: !Unpin ; невыполнение этого будет означать, что закрепленные ссылки на эти типы не будут безопасно предоставлять закрепленный доступ к их внутренней части (обратите внимание, что есть некоторый прецедент для этого с отношениями & mut и &: & mut & T не принудительно к & mut T, только & T, и Box <& mut T> не может быть приведен к Box<T> , только & mut T; в общем случае, когда различные типы-состояния сталкиваются, они не должны автоматически распространяться). Я признаю, что обычно &mut T , Box<T> и T считаются полностью взаимозаменяемыми с точки зрения состояния типов, и этот аргумент не распространяется на гипотетические встроенные экзистенциальные объекты, но, возможно, это так. суть предложения DynSize (я полагаю, не разрешать безопасные перестановки или mem::replace s для значений объектов признаков? На самом деле, для них это уже запрещено ... но я предполагаю, что есть какая-то причина, по которой это может измениться в будущем). Это делает решение времени компиляции очень простым: для любой структуры, которая (транзитивно) напрямую владеет (без & mut, &, Box или необработанными указателями - ни один из них не будет безопасно транзитивно распространять доступ к Pin , за исключением & для общего пина, но & в любом случае нельзя переместить - или если вы решили использовать решение «не может выйти из объектов признаков», вы может также транзитивно проверять поля вдоль &mut и Box я думаю) и имеет доступ (в смысле видимости) к известному типу !Unpin (включая себя самого), он должен реализовать второй вид drop . Мне кажется, что это совершенно нормально и вообще не является инструментом обратной совместимости, поскольку нет стабильных типов !Unpin людям, возможно, придется переопределить деструкторы после обновления библиотеки, но это все, правильно? Более того, внутренние детали реализации останутся внутренними; только если будет открыто поле !Unpin возникнут какие-либо проблемы. Наконец, все общие типы контейнеров (не только Vec и стандартные библиотеки, но по сути вся экосистема crates.io) продолжат работать нормально. Что такого катастрофического, что мне не хватает в этом решении?

(На самом деле мне кажется, что даже если вы не смогли применить это во время определения drop , вы, по крайней мере, должны иметь возможность сделать это во время создания экземпляра типа, как это делает dropck , поскольку вам нужно беспокоиться только о полностью созданных типах).

Перечитывая предложение Dynsized : я заметил, что аргумент против него требовал, чтобы неподвижные типы всегда были DynSized даже до того, как они будут закреплены. Выше я утверждал, что нам действительно нужно беспокоиться об этом только в случае объектов-признаков; возможно (хотя и трудно оправдать) принудительное применение этого принуждения типа !Unpin к признаку, требующему явного ограничения его с помощью + ?DynSized (или чего-то еще; это может быть сделано автоматически, я полагаю). Хотя может быть много случаев, когда размер должен быть известен для типов !Unpin (действительно, у меня есть такой вариант использования!), Или они должны иметь возможность менять местами, прежде чем они будут закреплены, я надеюсь, что есть очень мало таких случаев для внутренней части характерных объектов, созданных из неподвижных типов (единственные случаи, которые у нас есть сейчас, - это такие вещи, как преобразование Box -> Rc которое мы хотим явно запретить, верно? На самом деле мне даже не ясно, является ли раскрытие size_of_val действительно проблемой здесь или нет, поскольку я до сих пор не знаю, сможете ли вы превратить Pin<'a, Box<Trait> в Pin<'a, Trait> , но если вы не можете, мы можем просто положиться на Sized конечно). В любом случае очень хочется иметь возможность связать их с помощью !Unpin но, как я уже сказал, я думаю, что люди хотят избежать добавления большего количества отрицательных черт, чем нам уже нужно (лично я надеюсь, что !Unpin объекты-признаки будут достаточно редкими и специализированными, так что ограничивающие объекты-признаки с ?Unpin а не Unpin будут вполне разумными и не слишком сильно заразят систему типов; большинство применений !Unpin Я могу придумать, что Pin<self> . Вы также обычно хотите использовать их с PinBox или PinTypedArena или что-то еще, в этот момент границы ?Unpin выглядят довольно естественно).

У меня новый дизайн, который, я думаю, не так ужасен, как старый: https://github.com/pythonesque/pintrusive/blob/master/src/main.rs. Вместо того, чтобы пытаться заставить проекции выводов работать повсюду, этот дизайн спрашивает, как мы можем взаимодействовать с «устаревшим» кодом Rust, который ничего не знает о закреплении, от «современного» кода Rust, который всегда хочет поддерживать закрепление. Очевидный ответ, кажется, использует предложенную черту PinDrop @RalfJung , но только для типов, которые имеют настраиваемое перетаскивание и хотят проецировать поля.

Типы явно выбирают поля проецирования (соответствующие получению признака PinFields ), что аналогично коду, написанному на «современном» Rust; тем не менее, он не предъявляет дополнительных требований к коду в «унаследованном» Rust, вместо этого предпочитая поддерживать только проектирование на глубину 1 для любого заданного производного PinFields . Он также не пытается перемещать Pin по ссылкам, что, по моему мнению, в любом случае является хорошей идеей. Он поддерживает структуры и перечисления, включая любой анализ дизъюнктности, который Rust должен уметь обеспечивать, генерируя структуру с идентичными полями и вариантами, но с заменой типов на Pin 'd ссылок на типы (это должно быть чтобы расширить это до Pin и PinMut когда это изменение будет внесено). Очевидно, это не идеально (хотя, надеюсь, оптимизатор может избавиться от большей части), но у него есть то преимущество, что он работает с заимствованием и NLL и легко работает с перечислениями (в отличие от сгенерированных средств доступа).

Аргумент безопасности работает, гарантируя, что либо Drop вообще не реализован для структуры, либо гарантирует, что, если Drop реализован для нее, это тривиальная реализация, которая вызывает только PinDrop (версия Drop, которая принимает Pin). Я считаю, что это исключает все проблемы с надежностью проецирования закрепленных полей с одним вопросительным знаком: моя основная задача - найти хороший аргумент в пользу того, почему несвязанные поля в одном и том же контейнере (то есть поля на глубине 1) могут сделать недействительными друг друга булавки в их деструкторах уже были бы недопустимыми без выступов булавок. Я думаю , что я могу оправдать это , если мы можем показать , что вы могли бы также сделать то же самое , если бы они были проведены в отдельных PinBoxes, что означает , что там ,

@pythonesque Я действительно не следил за тем, что вы написали выше о DynSize , но я так понимаю, что все это уже устарело? Итак, я собираюсь прокомментировать только ваш последний пост.

Подводя итог, вы говорите, что проецирование на поле структуры / перечисления (включая поля pub ) в целом небезопасно, но тип может отказаться от безопасных проекций поля, не реализовав Drop . Если типу нужен деструктор, он должен PinDrop вместо Drop :

trait PinDrop {
  fn pin_drop(self: PinMut<Self>);
}

У нас уже есть проверка типов для Drop чтобы отклонить выход за пределы поля, поэтому не кажется нереалистичным также проверить Drop чтобы отклонить проецирование через &pin . Конечно, проверка «выхода за пределы поля» все равно будет отклонена, если есть PinDrop , тогда как в этом случае проекция будет разрешена.

Компилятор применит к PinDrop те же ограничения, что и к Drop , плюс он будет гарантировать, что тип не реализует одновременно Drop и PinDrop . При создании клея для капель он вызывает любой тип Drop , реализованный в этом типе.

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


Теперь, конечно, мне интересно, каковы здесь обязательства по доказательству. Я думаю, что самый простой способ увидеть это - сказать, что PinDrop на самом деле является основным и единственным формально существующим деструктором, а impl Drop for T на самом деле является синтаксическим сахаром для impl PinDrop который вызывает небезопасный метод PinMut::get_mut а затем вызывает Drop::drop . Это автоматически генерируются небезопасный код, однако, поскольку пиннинг является «локальным расширением» (то есть, она имеет обратную совместимость с существующими небезопасным кодом), что небезопасный код всегда безопасен , если данный тип не заботится о пиннинге.

Говоря более формально, существует «инвариант закрепления по умолчанию», который есть у типов, если их автор не заботится о закреплении. Типы, использующие этот инвариант закрепления по умолчанию, автоматически становятся Unpin . Запись impl Drop для типа с настраиваемым инвариантом утверждает, что тип использует «инвариант закрепления по умолчанию». Это немного подозрительно, так как кажется, что здесь было обязательство доказательства, но нет unsafe чтобы предупредить об этом - и действительно, это не идеально, но важна обратная совместимость, так что вы можете делать. Это тоже не катастрофа, потому что можно утверждать, что на самом деле это означает, что это изменяет обязательство доказательства, которое возлагается на небезопасный код в другом месте, настолько, что этот небезопасный код должен работать с «инвариантом закрепления по умолчанию». Если в этом модуле нет небезопасного кода, все в порядке.

Я мог даже представить, что мы могли бы добавить линт против impl Drop for T если только T: Unpin , возможно, ограничился случаем, когда модуль, определяющий тип, имеет в себе небезопасный код. Это было бы место, чтобы рассказать людям о проблеме и побудить их либо unsafe impl Unpin (формально утверждая, что они используют инвариант закрепления по умолчанию), либо impl PinDrop .

@RalfJung

Обобщает ли это предложение?

Да, более или менее (я думаю, что ваше предложение на самом деле значительно более амбициозно, чем мое, поскольку оно, похоже, предлагает автоматическое выполнение прогнозов, если Drop не реализован, что, по моему мнению, вероятно, является проблемой прямой совместимости для библиотек; но, возможно, есть какой-то способ обойти тот).

Часть, которую я не понимаю, - это ваш последний абзац. Не могли бы вы привести пример, демонстрирующий, что вас беспокоит?

Вообще говоря: меня беспокоят два поля, которые живут непосредственно в одной и той же структуре, одно из которых изменяет другое при вызове его Drop (возможно, полагаясь на такие вещи, как порядок отбрасывания для безопасности) таким образом, что нарушает инварианты закрепления другое поле, но сохраняет свою структурную целостность (что позволяет наблюдать нарушения инварианта закрепления). Это, очевидно, не может использовать полностью безопасный код (иначе он, среди прочего, будет отклонен dropck), поэтому меня беспокоит только некоторый гипотетический случай, когда деструкторы типов полей можно было безопасно запускать, когда поля закреплены в отдельных конструкции, но небезопасно, когда они закреплены в одной конструкции. Я надеюсь, что таких случаев не будет, если нет общего инварианта, который включает структуру, в которой содержатся поля; если такой инвариант существует, мы знаем, что он должен знать, что его компоненты не соблюдают пользовательский инвариант должным образом, и поэтому мы можем винить код где-то в модуле.

Я думаю, что самый простой способ увидеть это - сказать, что PinDrop на самом деле является основным и единственным формально существующим деструктором, а impl Drop for T на самом деле является синтаксическим сахаром для impl PinDrop, который вызывает небезопасный метод PinMut :: get_mut, а затем вызывает Drop :: drop.

Согласовано. К сожалению, этот путь не был открыт для текущего решения, поскольку оно пытается делать что-то в библиотеке.

Говоря более формально, существует «инвариант закрепления по умолчанию», который есть у типов, если их автор не заботится о закреплении. Типы, использующие этот инвариант закрепления по умолчанию, автоматически открепляются. Написание impl Drop для типа с настраиваемым инвариантом утверждает, что тип использует «инвариант закрепления по умолчанию». Это немного подозрительно, так как кажется, что здесь было обязательство доказывать, но нет ничего опасного, чтобы об этом предупреждать - и действительно, это не идеально, но обратная совместимость важна, так что вы можете сделать.

Да, это примерно то, что я имел в виду ... Меня просто беспокоит то, что у меня нет хорошей интуиции, что означало бы фактически формализовать эту гарантию "небезопасного кода в модуле". Мне действительно нравится ваша идея линта (на самом деле, мне нравится все, что привлекает больше людей, использующих PinDrop!), Но я думаю, что unsafe impl Unpin , вероятно, будет слишком часто ошибаться, чтобы его можно было предложить, на по крайней мере, для типов с общими общедоступными полями (но опять же, для структур, которые не имеют таких полей, это действительно будет довольно часто ... это в основном верно для стандартной библиотеки, например).

Я написал пример того, как может работать #[derive(PinnedFields)] : https://github.com/withoutboats/derive_pinned

Я видел, как люди утверждали, что подобные производные «неверны», но, черт возьми, это неправда. Вам нужно будет использовать небезопасный код, чтобы сделать что-то, что могло бы конфликтовать с кодом, сгенерированным этой производной, то есть это делает другой небезопасный код небезопасным (и я думаю, что этот код - перемещение по ?Unpin data - это то, что вы всегда можете / должны избегать).

РЕДАКТИРОВАТЬ: Хорошо, на самом деле прочитайте последние несколько сообщений о деструкторах. Обработаем.

@withoutboats Да, я думаю, вы это уже видели, но проблема заключалась в том, что небезопасный код в правильном типе !Unpin мог быть признан недействительным безопасным деструктором для типа, производного от PinFields (так в модуле не было небезопасного кода для типа, производного от PinFields за исключением автоматически сгенерированного материала). Это проблемная часть. Однако взгляните на дизайн, который я связал (игнорируя стилистический выбор для создания отдельной структуры, а не получения отдельных аксессуаров - это просто я пытался заставить его поддерживать как можно больше вариантов использования с помощью средства проверки заимствований). Некоторое время я волновался, но теперь я почти уверен, что #[derive(PinFields)] все еще может работать, просто нужно позаботиться о том, чтобы Drop не реализовывалось напрямую.

Я также хочу поднять еще один момент, о котором я думал (который я еще нигде не видел напрямую решенным?): Я думаю, что что-то, что сделало бы Pin намного более удобным для использования и лучше интегрировалось бы в существующий код, будет твердо стоять на стороне того, что практически все типы указателей будут Unpin . То есть создание &mut T , &T , *const T , *mut T , Box<T> и так далее считается Unpin для любых T . Хотя может показаться подозрительным, чтобы что-то вроде, скажем, Box<T> было Unpin , это имеет смысл, если учесть, что вы не можете получить Pin внутри Box из Pin<Box<T>> . Я думаю, что это позволяет !Unpin заражать только «встроенные» объекты - это очень разумный подход - у меня нет единственного варианта использования, позволяющего закреплению стать вирусным по ссылкам, и это делает семантику любой возможный тип &pin очень приятен (я разработал таблицу того, как он будет взаимодействовать с другими типами указателей в этом сценарии, и, по сути, если вы игнорируете ходы, это заставляет &mut pin действовать так же, как &mut , &pin действуют так же, как & , а box pin действуют так же, как box отношении взаимосвязи с другими указателями). Насколько я могу судить, это также не важно с операционной точки зрения: в общем случае перемещение значения типа A содержащего указатель на значение типа B , не перемещает указанное значение введите B , если значение типа B не встроено в значение типа A но если это так, тогда A автоматически становится !Unpin поскольку он содержит B без косвенного обращения к указателю. Возможно, что наиболее важно, это будет означать, что большой процент типов, которые в настоящее время нуждаются в небезопасной ручной реализации !Unpin , не будет нуждаться в этом, поскольку большинство коллекций содержат только T за косвенным указателем. . Это позволит как текущему Drop продолжить работу, так и позволит этим типам реализовать PinDrop без изменения их семантики (поскольку, если тип равен Unpin он может обрабатывать Pin 'd аргумент как &mut любом случае).

Мне может не хватать какой-то причины, по которой такой переходный закрепление было бы хорошей идеей, но пока я не нашел ни одной. Единственное, что меня может беспокоить, это то, возможно ли на самом деле реализовать это поведение с автоматическими чертами - я думаю, что, вероятно, будет, но, возможно, в некоторых случаях, когда люди используют PhantomData<T> но на самом деле имеют указатель на T , было бы неплохо изменить его на PhantomData<Box<T>> . Обычно мы думаем, что они семантически идентичны, но с закреплением это не совсем так.

@pythonesque Терминология "нового языка" и все такое для меня как бы неуставно. Мое впечатление о том, что вы делаете:

  • По умолчанию #[derive(PinFields)] генерирует бездействие Drop impl. Это гарантирует, что вы никогда не получите доступ к закрепленному полю в деструкторе.
  • Необязательный атрибут изменяет этот Drop impl на вызов PinDrop::pin_drop , и вы должны реализовать PinDrop .

Это правильно?


Я также считаю, что все это имело значение только в том случае, если мы расширили гарантии Pin для поддержки навязчивых коллекций. Это согласуется с вашим пониманием @RalfJung и @pythonesque?


Все это довольно неприятно, потому что кажется очевидным, что Drop просто должен взять себя на Pin ! Внесение более радикальных (возможно, эпохальных) изменений кажется привлекательным, но я не вижу способа, который не был бы настолько разрушительным.

Вот пример некорректного доступа к закрепленным полям + drop: https://play.rust-lang.org/?gist=8e17d664a5285e941fe1565ce0eca1ea&version=nightly&mode=debug

Тип Foo передает внутренний буфер некоторому внешнему API, который требует, чтобы буфер оставался на месте до тех пор, пока он не будет явно отключен. Насколько мне известно, это звучит при ограничениях, предложенных @cramertj , после создания Pin<Foo> вам гарантируется, что он не будет перемещен до тех пор, пока для него не будет вызван Drop (с при условии, что вместо этого может произойти утечка и Drop никогда не вызывается, но в этом случае вам гарантируется, что он никогда не будет перемещен).

Тип Bar затем прерывает это, перемещая Foo во время реализации Drop .

Я использую структуру, очень похожую на Foo для поддержки периферийного радиоустройства, которое обменивается данными через DMA, у меня может быть StableStream с внутренним буфером, в который записывается радио.

@withoutboats

Это правильно?

Да, за исключением того, что на самом деле он не генерирует бездействующий Drop impl (потому что типы, которые не реализуют Drop в Rust, в целом работают лучше). Вместо этого он пытается утверждать, что Drop не был реализован с использованием некоторых подозрительных функций библиотеки (он работает в стабильной версии, но не работает при специализации - я думаю, что есть вариант, который должен работать при специализации, но не сейчас, потому что проблем со связанными константами). Если бы это была языковая функция, это было бы довольно легко реализовать.

Я также считаю, что все это имело значение только в том случае, если мы расширили гарантии Pin для поддержки навязчивых коллекций. Это согласуется с вашим пониманием @ralfj и @pythonesque?

Нет, к сожалению, это не так. Приведенный выше контрпример не имеет ничего общего с дополнительными гарантиями для навязчивых коллекций. Тип Pin 'd уже должен иметь возможность предполагать, что он не будет перемещаться, прежде чем он будет использоваться снова, даже без этой гарантии, поскольку, если метод вызывается дважды для значения из-за закрепленной ссылки, значение имеет нет возможности узнать, перемещался ли он между двумя вызовами. Дополнительные гарантии, которые необходимы, чтобы сделать его полезным для навязчивых коллекций, добавляют дополнительную вещь о необходимости вызывать drop перед освобождением памяти, но даже без этой гарантии drop все равно можно вызвать для чего-то который в настоящее время закреплен (например, за PinBox). Если что-то, что отброшено, включает встроенные поля, и мы разрешаем проецировать булавку из того, что было сброшено в эти поля, то деструктор внешнего типа все еще может перемещать внутреннее поле, закреплять его снова (помещая перемещенный значение поля в PinBox , например), а затем вызовите для него методы, которые ожидают ссылок с того момента, когда он был закреплен ранее, чтобы по-прежнему оставаться действительными. Я действительно не вижу другого выхода; пока вы можете реализовать drop , у вас может быть эта проблема для любого встроенного поля !Unpin . Вот почему я думаю, что наименее плохое решение - не позволять людям реализовывать drop если они хотят, чтобы закрепление работало правильно.

Все это довольно неприятно, потому что кажется очевидным, что Drop просто должен взять себя за Pin! Внесение более радикальных (возможно, эпохальных) изменений кажется привлекательным, но я не вижу способа, который не был бы настолько разрушительным.

Да, это действительно раздражает ... Я очень хмурился из-за этого около недели. Но, как заметил Ральф, нам пришлось бы ждать версии 1.0 еще три года, прежде чем кто-нибудь это поймет ... и всегда будет что-то еще.

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

Выделенная часть кажется мне очень важной; на самом деле, это похоже на суть проблемы.

Сначала я представил себе этот код, который использует наш единственный метод, который действительно заботится о внутренних адресах:

struct TwoFutures<F>(F, F);

impl Drop for TwoFutures {
     fn drop(&mut self) {
          mem::swap(&mut self.0, &mut self.1);
          unsafe { Pin::new_unchecked(&mut self.0).poll() }
     }
}

Но для возврата к Pin ! Требуется небезопасный код. Этот код просто следует считать ненадежным.

Можем ли мы исключить возможность для методов, которые принимают &self и &mut self полагаться на достоверность внутренних адресов для обеспечения надежности? На что это похоже?

@withoutboats Даже если только методы, которые принимают Pin<Self> полагаются на достоверность внутренних адресов, проблема все равно может возникнуть. Деструктор внешнего типа может mem::replace поле внутреннего типа (используя свою собственную ссылку &mut self ), затем PinBox::new значение, а затем вызвать закрепленный на нем метод . Небезопасно не требуется. Единственная причина, по которой это не проблема без возможности проецирования закрепленных полей, заключается в том, что это считается приемлемым для типа, который на самом деле реализует странные методы !Unpin с использованием unsafe (или разделяет инвариант с типом, который ), чтобы иметь обязательства по доказательству своей реализации drop даже если там используется только безопасный код. Но вы не можете спросить это у типов, которые случайно содержат тип !Unpin и не имеют собственного небезопасного кода.

@pythonesque

Я думаю, что ваше предложение на самом деле значительно более амбициозно, чем мое, поскольку оно, кажется, предлагает автоматическое выполнение прогнозов, если Drop не реализован, что, по моему мнению, вероятно, является проблемой прямой совместимости для библиотек; но, может быть, есть способ обойти это

Что ж, я думаю, что мы хотим это сделать в конце концов. Как это проблема совместимости?

Меня беспокоят два поля, которые живут непосредственно в одной и той же структуре, одно из которых изменяет другое при вызове его Drop (возможно, полагаясь на такие вещи, как порядок отбрасывания для безопасности) таким образом, чтобы нарушить инварианты закрепления другого поля, но сохраняет свою структурную целостность (что позволяет наблюдать нарушения инварианта закрепления).

Но в настоящее время это уже было бы незаконным. Вы не должны нарушать чужие инварианты.

но я думаю, что unsafe impl Unpin, вероятно, слишком часто будет ошибаться, чтобы его можно было предложить, по крайней мере, для типов с общими общедоступными полями

Я думаю, что в большинстве случаев это действительно будет правильно - я ожидаю, что большинство людей не будут предоставлять никаких аксессуаров для Pin<Self> , и в этом случае они, вероятно, будут использовать инвариант закрепления по умолчанию, и, следовательно, это нормально для unsafe impl Unpin за них.

Я думаю, что что-то, что сделало бы Pin намного более удобным в использовании и лучше интегрировалось в существующий код, заключалось бы в том, чтобы твердо встать на сторону, по существу, всех типов указателей, являющихся Unpin. То есть, делая & mut T, & T, * const T, * mut T, Box, и так далее, все они считаются откреплением для любого T. Хотя может показаться подозрительным разрешить что-то вроде, скажем, BoxЧтобы открепить, это имеет смысл, если учесть, что вы не можете получить булавку внутри коробки из булавки>.

Я согласен, что это, скорее всего, произойдет (также см. Мой комментарий на https://github.com/rust-lang/rust/pull/49621#issuecomment-378286959). В настоящее время я считаю, что нам следует немного подождать, пока мы не почувствуем себя более уверенно во всем, что касается закрепления, но на самом деле я не вижу особого смысла в принудительном закреплении за пределами косвенных указателей.
Я не уверен, как я отношусь к тому, чтобы делать это с необработанными указателями, хотя мы обычно крайне консервативны в отношении авто-трейтов для них, поскольку они используются самыми разными безумными способами.


@withoutboats

Я написал пример того, как может работать # [derive (PinnedFields)]: https://github.com/withoutboats/derive_pinned

@pythonesque уже сказал об этом, но для ясности: эта библиотека не работает. Используя его с проблемой падения @MicahChalmer, я могу сломать любой закрепленный тип, который на самом деле зависит от закрепления. Это не зависит от дополнительной гарантии вызова drop. Сообщите мне, нужен ли еще пример. ;)

@RalfJung

Эта библиотека не работает.

Для пояснения, производное - это просто unsafe , и его нельзя использовать в сочетании с ручным Drop impl. Используя наследование, вы обещаете не делать ничего из плохого, перечисленного в реализации Drop .

https://github.com/rust-lang/rust/pull/50497 изменяет API закрепления, самое главное переименовывает Pin в PinMut чтобы освободить место для добавления общего Pin в будущем.


Чтобы уточнить, наследование просто небезопасно и не может использоваться в сочетании с ручным внедрением Drop.

Согласен, сделать это небезопасно сработает. Хотя тест показывает, что в настоящее время он не помечен как небезопасный?

@RalfJung Верно, я не думаю, что сейчас это так. Другой вариант, о котором я только что подумал, - это создать в «безопасной» версии реализацию Drop для типа, блокируя другие ручные импликации Drop . Чтобы отключить это, может быть флаг unsafe . WDYT?

@cramertj да, это тоже должно работать. Однако это будет иметь побочные эффекты, такие как более ограниченное выпадение и невозможность покинуть поля.

В понедельник нам с @pythonesque позвонили, чтобы обсудить это. Вот резюме.

Мы пришли к выводу, что, вероятно, «правильным» поведением было бы, если бы Drop взял себя за пин. Однако переход к этому обескураживает. Хотя я полагаю, что это возможно с изменением редакции в той или иной форме, это было бы чрезвычайно разрушительно.

Обратно совместимое изменение состоит в том, чтобы просто сделать его каким-то образом некогерентным для типов, которые можно «спроецировать» для реализации Drop . В своем репозитории @pythonesque реализовал это, сгенерировав импы из запутанного набора трейтов.

Можно представить себе встроенную форму, которая немного проще:

  • Маркерная черта, назовем ее PinProjection , определяет, можно ли проецировать тип булавками. Невозможно (благодаря встроенной в компилятор магии) реализовать как PinProjection и Drop .
  • Другая черта, PinDrop , расширяет PinProjection чтобы предоставить альтернативный деструктор для Drop .

Производные для создания методов проецирования булавок, подобных тем, которые я показал и @pythonesque, также будут генерировать имплицитные значения PinProjection для типа.

@withoutboats

Мы пришли к выводу, что, вероятно, «правильным» поведением было бы, чтобы Drop брал себя за булавку. Однако переход к этому обескураживает. Хотя я полагаю, что это возможно с изменением редакции в той или иной форме, это было бы чрезвычайно разрушительно.

Просто интересно, ЕСЛИ мы однажды захотели сделать такую ​​вещь, будет ли то, что вы предлагаете, совместимо с будущим?

Например, допустим, мы решили ...

  • заставить Drop волшебным образом принимать как закрепленные, так и незакрепленные формы, ИЛИ
  • переписать все подписи Drop для них в рамках автоматического обновления до Rust 2021 :)

(Я не предлагаю ни то, ни другое, но, кажется, трудно ответить на этот вопрос без конкретных примеров.)

@tmandry В этом

Большая проблема - это общая реализация Drop которая перемещает параметр типа:

struct MyType<T>(Option<T>);

impl<T> Drop for MyType<T> {
    fn drop(&mut self) {
        let moved = self.0.take();
    }
}

Автоматическое обновление, о котором вы говорите, просто нарушит этот Drop impl, который действителен только в том случае, если мы добавим границу, которая T: Unpin .

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

@withoutboats

Другая черта, PinDrop, расширяет PinProjection, предоставляя деструктор, альтернативный Drop.

Почему PinDrop расширяет PinProjection ? Это кажется ненужным.

Также это можно было бы сделать немного умнее, сказав, что PinProjection и Drop несовместимы, если только тип не Unpin (или найти другой способ удаления всех этих новых различий / ограничений. для типов Unpin ).

@RalfJung, мы бы хотели, чтобы PinDrop и Drop как-то исключали друг друга. В конечном итоге есть несколько способов реализовать это с разными компромиссами; Я думаю, что для этого нам нужен RFC, потому что это немалое изменение.

@withoutboats Согласовано. Я подумал, что эксклюзивность может возникнуть из-за особой обработки PinDrop , но, очевидно, есть несколько способов сделать это. Я думаю, было бы очень полезно, если бы Unpin типы не заботились; вместе с тем, чтобы сделать все типы указателей Unpin безоговорочно, это, вероятно, поможет сократить количество случаев, когда люди даже должны знать об этом.

@withoutboats Также стоит отметить, что основные случаи, которые я могу придумать, где люди хотят, скажем, перемещать общие данные, в Vec в деструкторе (я выбрал Vec потому что люди IIRC хотели бы реализовать методы на Vec которые действительно заботятся о Pin , что означает, что он не может безоговорочно реализовать Unpin ), на самом деле это &mut Vec<T> или Vec<&mut T> или что-то в этом роде. Я в основном говорю об этом, потому что это случаи, которые, вероятно, были бы сбиты с толку предложенным lint PinDrop а не на unsafe impl Unpin (теоретически они всегда могут сделать первое, конечно, связав T с Unpin в типе, но это будет критическим изменением для клиентов библиотеки).

Кроме того, стоит отметить, что, хотя это действительно добавляет еще несколько черт, чем мы хотели бы, черты - это те, которые почти никогда не будут отображаться в чьей-либо сигнатуре типа. В частности, PinProjection - это совершенно бессмысленная привязка, если вы не пишете макрос #[derive(PinFields)] потому что компилятор всегда (я верю) сможет определить, выполняется ли PinProjection если он может выяснить, что это за поля в типе, и единственное, для чего он полезен, - это проектирование полей. Точно так же PinDrop никогда не должно быть привязкой к чему-либо по той же причине, что Drop почти никогда не используется в качестве привязки. Даже объекты признаков не должны быть затронуты (я полагаю, если когда-нибудь мы получим «связанные поля», но с новой функцией, подобной этой, мы могли бы поручить, чтобы черты со связанными полями были реализованы только для типов PinProjection , что аккуратно решило бы эта проблема).

@RalfJung

Что ж, я думаю, что мы хотим это сделать в конце концов. Как это проблема совместимости?

Я полагаю, что это не что иное, как добавление типа, который не реализует Send или Sync , разница в том, что ни один из них не влияет на основной язык в той же степени, что и это. Выполнение этого полностью автоматически похоже на то, как Rust обрабатывал Copy (типы всегда были Copy если они содержали только типы Copy и не реализовывали Drop ), который в конечном итоге был изменен, чтобы сделать его явным, потому что, по-видимому, людям не нравился тот факт, что добавление признака ( Drop ) могло сломать общий код, даже если они этого не осознавали (поскольку весь смысл согласованности заключается в том, что дополнительный реализации трейтов не должны нарушать последующие ящики). Это кажется почти идентичным, только с PinProjection вместо Copy . Мне действительно нравилось старое поведение, я просто думаю, что было бы трудно оправдать PinProjection работающий таким образом, когда Copy - нет.

Но в настоящее время это уже было бы незаконным. Вы не должны нарушать чужие инварианты.

Да, чем больше я думаю об этом, тем менее правдоподобным кажется, что это может быть проблемой.

Я думаю, что в большинстве случаев это будет правильно

Ну, да, но только потому, что большинство типов не реализуют никаких методов, требующих Pin или не предоставляют свои общие поля как общедоступные. В то время как последнее вряд ли изменится, первое - нет - я ожидаю, что по крайней мере некоторые из типов stdlib, которые в настоящее время являются !Unpin добавят методы проектирования выводов, после чего реализация unsafe больше не действует. Мне это кажется довольно хрупким. Более того, меня беспокоит увеличение количества «шаблонного» небезопасного кода, который приходится писать людям; Я думаю, что границы Send и Sync будут unsafe impl правильны примерно так же часто, как они реализованы неправильно, а Unpin будет еще более коварным, потому что обычно правильная версия не имеет ограничений на T . Так что кажется гораздо предпочтительнее направлять людей к PinDrop (хотя я понимаю, почему вы опасаетесь делать это для типов необработанных указателей. Я просто беспокоюсь, что уже - unsafe код будет даже более вероятно, что вы сделаете unsafe impl не задумываясь, но чем больше я думаю об этом, тем больше кажется, что значение по умолчанию для необработанных указателей, вероятно, правильное, и пометить его с помощью вашего lint было бы полезно).

Я думаю, было бы очень полезно, если бы типы Unpin не заботились.

Я вроде как согласен, но поскольку, как я уже сказал, люди не будут использовать PinProjection в качестве фактического ограничения, я не уверен, насколько это будет иметь значение на практике. Уже есть реализация DerefMut для PinMut где T: Unpin так что сегодня вы не получите от нее никакой пользы. Правило, реализованное в компиляторе (для проекций), предположительно превратит PinMut::new в своего рода &pin reborrow для типов Unpin , но на самом деле это не имеет ничего общего с проекциями полей. А поскольку для вывода PinProjection не требуется PinProjection для своих полей, вам не понадобится это только для удовлетворения границ derive для другого типа. Так в чем же выгода? Единственное, что он действительно позволит вам сделать, - это реализовать Drop и одновременно получить PinProjections , но мы всегда хотели бы, чтобы люди реализовали PinDrop если это возможно, так что это быть чистым отрицательным ИМО.

Я выбрал Vec, потому что люди IIRC хотели бы реализовать методы на Vec, которые действительно заботятся о Pin, а это означает, что он не может безоговорочно реализовать Unpin

Хм, не думаю, что мне это нравится. Если Box безусловно Unpin , я считаю, что Vec должно быть таким же. Эти два типа обычно в значительной степени эквивалентны по своему владению.

Также мы должны быть осторожны с гарантией drop ; Например, Vec::drain может привести к утечке информации в случае паники.

Это кажется почти идентичным, только с PinProjection вместо Copy

О, теперь я понимаю твой вопрос. На самом деле я не говорил об автоматическом выводе PinProjections поскольку в моем предложении не было такой черты, но на самом деле одним из следствий моего предложения было бы то, что добавление Drop является критическим изменением, если вы есть публичные поля.

Единственное, что он действительно позволит вам сделать, - это реализовать Drop и извлекать PinProjection одновременно, но мы всегда хотели бы, чтобы люди реализовали PinDrop, если это возможно, так что это было бы чистым отрицательным IMO.

В этом и был смысл. Чем меньше люди будут заботиться обо всем этом, тем лучше.

Уточняющий вопрос: в вашем предложении и предложении @withoutboats PinDrop является элементом языка и рассматривается компилятором как замена Drop , или это просто признак, используемый derive(PinProjections) для реализации Drop ?

Также мы должны быть осторожны с гарантией падения; Например, Vec :: сток может привести к утечке информации в случае паники.

Ну, это правда, но на самом деле это не освобождает память, верно? Чтобы было ясно, что я на самом деле предпочитают , если Vec было безоговорочно реализовать Unpin , так как это облегчит для людей , чтобы просто переключиться на PinDrop , но определенно говорить некоторые из закрепляющих дискуссий о том, как можно прикреплять к элементам поддержки. Как только вы начнете говорить о закрепленных полях , станет ясно, что вы не всегда можете просто превратить Vec в Box<[T]> на месте, а затем закрепить его, так что оно может иметь некоторую ценность на этот момент (хотя, очевидно, вы также можете добавить тип PinVec вместо типа Vec , как это было сделано для Box ).

В этом и был смысл. Чем меньше люди будут заботиться обо всем этом, тем лучше.

Хм, с моей точки зрения, поначалу это было бы правдой, но в долгосрочной перспективе мы хотели бы перевести как можно больше людей на значение по умолчанию PinDrop , особенно если тип действительно беспокоил #[derive(PinProjections)] (который, опять же, даже не понадобился бы для каких-либо целей, если бы тип был Unpin вероятно, это произошло бы только в таких вещах, как сгенерированный код). Затем (возможно, после того, как &pin какое-то время был на языке), у вас может быть смена эпохи, которая переключит Drop на DeprecatedDrop или что-то в этом роде. Для типов Unpin простое изменение сигнатуры типа с &mut на &mut pin в значительной степени решило бы все проблемы, так что, возможно, это действительно не нужно.

Разъяснение вопрос: В вашей и @withoutboats «ы предложение, является PinDrop пункт языки и обрабатывают компилятором в качестве замены для Drop, или это просто признак используется Dérivé (PinProjections) осуществлять Капля?

Бывший.

Все это обсуждение Drop кажется довольно радикальным упущением со стороны оригинального RFC. Было бы полезно открыть новый RFC для обсуждения этого? Трудно понять, что вызывает беспокойство, какие проблемы более опасны, чем другие, сколько именно машин нам нужно добавить к языку, чем мы первоначально думали, сколько поломок нам следует ожидать (и будет ли это может быть смягчено выпуском) в худшем и лучшем сценариях, как мы можем перейти к чему-либо, что удастся ударить Drop, и сможем ли мы выполнить все это и при этом достичь наших целей на 2018 год с помощью других средств (например, как https: //github.com/rust-lang/rfcs/pull/2418 предлагает скрыть Pin от всех общедоступных API, чтобы не блокировать стабилизацию).

@bstrie Я думаю, что есть только одна серьезная проблема, но она довольно существенная. RFC был одобрен с пониманием того, что возможный тип &pin mut в какой-то момент может сделать такой код безопасным:

struct Foo<T> { foo : T }

fn bar<T>(x: &pin mut Foo<T>) -> &pin mut T {
  &pin mut x.foo
}

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

К сожалению, безопасно ли это, зависит от реализации Drop для Foo . Поэтому, если мы хотим поддерживать безопасные проекции полей (будь то с помощью языковой функции, пользовательского #[derive] или любого другого механизма), мы должны быть в состоянии гарантировать, что такие плохие реализации Drop могут ' не случилось.

Альтернатива, которая кажется наиболее эффективной, позволяет вышеуказанному коду работать без использования unsafe (только с добавлением #[derive(PinProjections)] поверх структуры) и не нарушает существующий код. . Его можно добавить обратно совместимо, даже если Pin уже стабилизировано и даже может быть добавлено как чистая библиотека (с серьезным эргономическим ударом). Он совместим как с макросами #[derive] для создания средств доступа, так и с возможным исходным ссылочным типом &pin . Хотя для этого требуется добавить по крайней мере одну новую характеристику (и, возможно, две, в зависимости от того, как реализована закрепленная версия Drop ), они никогда не должны появляться где-либо в предложениях where или объектах признаков, и новая версия drop должна быть реализована только для типов, которые в настоящее время имеют реализацию Drop и хотят выбрать проекцию контактов.

То, что у решения не так много недостатков, не означает, что это несущественное изменение, поэтому я думаю, что мы почти наверняка создадим RFC для этого предложения ( @withoutboats, и я обсуждал это во время

У большинства людей PinMut в общедоступных API вызывает беспокойство именно то, что для этого потребуется либо unsafe либо поточная передача через границы Unpin везде, и это предложение решает эту проблему. Альтернативы, обсуждаемые в rust-lang / rfcs # 2418, кажутся гораздо более противоречивыми, как с точки зрения фактической механики того, как он пытается избежать закрепления (что включает в себя распространение различных других свойств, которые появятся в общедоступных API и документации), так и в общей сложности решения. В самом деле, даже если бы закрепление было полностью решено, я думаю, что есть ряд вопросов, которые, по мнению людей, не были должным образом решены с помощью этого RFC, поэтому я думаю, что есть, по крайней мере, приличный шанс, что RFC, добавляющий проекции безопасных контактов, может закончиться быть принятым до этого.

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

  • Этот (можем ли мы сделать проектирование полей безопасным кодом?). Предстоит решить в предстоящем RFC (где будут изложены возможные стратегии реализации, а также другие альтернативы, которые мы рассмотрели, и почему от них пришлось отказаться). Все варианты предлагаемого разрешения обратно совместимы.
  • Должно ли закрепление значения типа !Unpin с ручным деструктором подразумевать дополнительную гарантию (что резервная память значения не становится недействительной до тех пор, пока не будет вызван деструктор), иначе говоря, «изменения для навязчивых коллекций»?

    Все еще не решено, в основном потому, что это могло нарушить предлагаемый API закрепления стека; если API-интерфейс закрепления стека, который сохраняет эту гарантию, можно заставить работать с async / await, большинство заинтересованных сторон, похоже, готовы принять это (кто-то уже пытался решить эту проблему с помощью генераторов, но rustc ICE'd).

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

    К счастью, пользователи закрепления для Future s могут игнорировать это помимо формы API закрепления стека. API закрепления стека на основе замыкания совместим с любым разрешением, поэтому пользователи Future не использующие async / await, могут использовать API на основе замыкания сегодня, не дожидаясь принятия решения. Решение больше всего влияет на людей, которые хотят использовать закрепление для других целей (например, для навязчивых коллекций).

  • Должны ли необработанные указатели быть безусловно Unpin ? Все еще не решено (я думаю, что я единственный, кто предложил это, и я все еще в значительной степени 50 на 50). Не будет обратно совместимой; Я маркировка это как противоречивый в первую очередь по этой причине.
  • Должны ли стандартные типы библиотек, такие как Vec создаваться безусловно Unpin или в них должны быть добавлены средства доступа к полям Pin ? По-прежнему не решен, и может потребоваться разрешение в каждом конкретном случае. Для любого заданного типа безопасной оболочки либо добавление средств доступа, либо безусловное создание типа Unpin имеет обратную совместимость.
  • Следует ли удалить PinMut::deref ? По сути, ответ кажется «нет», поскольку преимущества его сохранения намного перевешивают недостатки, и, похоже, существуют обходные пути для тех случаев, когда люди изначально хотели этого (в частности, Pin<RefCell<T>> ). Его изменение было бы обратно несовместимым.
  • Как нам предоставить средства доступа к полям в краткосрочной перспективе (игнорируя проблемы с выпадением)? По-прежнему не решено: на данный момент представлены два варианта: https://github.com/withoutboats/derive_pinned и https://github.com/pythonesque/pintrusive. Разрешение полностью обратно совместимо, так как после изменения drop это может быть сделано в макросе чисто в коде библиотеки.
  • Как мы должны предоставлять средства доступа к полям в долгосрочной перспективе (т.е. должны ли существовать пользовательские типы &mut pin и &pin Rust? Как должно работать повторное заимствование?). До сих пор не решен, но обратно совместим со всеми другими предлагаемыми изменениями (насколько мне известно) и, очевидно, может быть изменен до бесконечности.
  • Должны ли безопасные ссылочные типы безусловно быть Unpin ? Вроде решено (да, должно). Разрешение полностью обратно совместимо.
  • Если у нас есть общий тип Pin в дополнение к уникальному Pin one, чтобы сделать доступными полевые средства доступа (это не работает с &Pin потому что это ссылка на ссылка)? Решением было изменение Pin на PinMut и добавление типа Pin для общего случая. Это не было обратной совместимостью, но критическое изменение (с Pin на PinMut ) уже было сделано, и я почти уверен, что это уже фактически принято.

Я думаю, это в значительной степени покрывает это. Как вы можете видеть, все они (помимо необработанных указателей и решения deref, последнее из которых, похоже, в значительной степени решено на данный момент) имеют некоторый обратно-совместимый путь вперед, даже если закрепление было стабилизировано сегодня; Что еще более важно, тот факт, что мы можем разрешать проекции полей, означает, что использование закрепленного типа в вашем API не суждено стать решением, о котором вы позже пожалеете.

В дополнение к вышеупомянутым вопросам были и другие предложения, которые направлены на более фундаментальное переосмысление закрепления. Два, которые, как мне кажется, я понимаю лучше всего, - это предложение @comex о внесении !Unpin types !DynSized и предложение steven099 (на внутреннем уровне; извините, я не знаю имени Github) для есть новый безразмерный тип оболочки Pinned который делает внутренние компоненты неподвижными (что-то вроде оболочки ZST).

Опция !DynSized - довольно консервативная функция (в том смысле, что в Rust уже есть подобная черта), имеющая то преимущество, что она может уже понадобиться для работы с непрозрачными типами. В этом смысле он может быть даже менее агрессивным, чем предлагаемые изменения в Drop . У него также есть высокий потенциал: он автоматически решает проблему с помощью Drop потому что типы !Unpin будут !DynSized и, следовательно, никто не сможет выйти из них. Это заставит &mut T и &T автоматически функционировать как PinMut и Pin везде, где T было !DynSized , поэтому вы бы не Нет необходимости в большом количестве закрепленных версий типов и методов, работающих с &mut T часто можно заставить работать нормально (если бы они были изменены, чтобы не требовать привязки DynSized когда она им не нужна ).

Основным недостатком (помимо обычных опасений по поводу ?Trait ), похоже, является то, что тип !Unpin никогда не может быть перемещен, что сильно отличается от ситуации с закрепленными типами в настоящее время. Это означает, что составление двух закрепленных типов без использования ссылки на самом деле было бы невозможным (насколько я могу судить), и я не уверен, как и есть ли какое-либо предлагаемое решение для этого; IIRC, это должно было работать вместе с предложением &move , но я не уверен, какова предполагаемая семантика этого. Я также не понимаю (из предложения), как вы могли бы иметь с ним безопасные проекции поля, поскольку он полагается на непрозрачные типы; похоже, что для его использования вам придется использовать много небезопасного кода.

Безразмерный тип Pinned<T> несколько похож по духу, но он хочет обойти указанную выше проблему, позволяя вам обернуть тип в ZST, который делает его неподвижным (фактически действующим без размера). На данный момент в Rust нет ничего сопоставимого: PhantomData самом деле не включает экземпляр типа, а другие типы с динамическим размером генерируют жирные указатели и по-прежнему позволяют перемещаться (с использованием API, основанных на size_of_val ; это то, что ?DynSized было предназначено исправить, поэтому это предложение, вероятно, снова совмещает эту черту). Мне не кажется, что это предложение на самом деле решает проблему Drop если вы разрешаете безопасные прогнозы, и оно также кажется несовместимым с Deref , так что для меня преимущества перед Pin не так понятны.

если API закрепления стека, который сохраняет эту гарантию, можно заставить работать с async / await, большинство заинтересованных сторон, похоже, готовы принять это (IIRC кто-то уже пытался решить эту проблему с помощью генераторов, но rustc ICE'd)

Для справки, это, вероятно, относится к https://github.com/rust-lang/rust/issues/49537, который является результатом этой попытки API закрепления стека на основе вложенного генератора. Я не уверен, будет ли работать там время жизни, даже если ICE будет решен.

Все еще не решено, в основном потому, что это могло нарушить предлагаемый API закрепления стека; если API-интерфейс закрепления стека, который сохраняет эту гарантию, можно заставить работать с async / await, большинство заинтересованных сторон, похоже, готовы принять это (кто-то уже пытался решить эту проблему с помощью генераторов, но rustc ICE'd).

Я рассматриваю API закрепления стека на основе замыкания как решение этой проблемы с будущим (как только &pin станет языковым примитивом) для чего-то более эргономичного и проверенного компилятором. Существует также это решение на основе макросов, предложенное @cramertj.

Следует ли безоговорочно откреплять необработанные указатели? [...] Не будет обратно совместимой; Я маркировка это как противоречивый в первую очередь по этой причине.

Как вы думаете, почему добавление impl Unpin for Vec<T> обратно совместимо, а необработанные указатели - нет?

Основным недостатком (помимо обычных проблем, связанных с? Trait), кажется, что тип! Unpin никогда не мог быть перемещен, что сильно отличается от ситуации с закрепленными типами в настоящее время. Это означает, что составление двух закрепленных типов без использования ссылки на самом деле было бы невозможным (насколько я могу судить), и я не уверен, как и есть ли какое-либо предлагаемое решение для этого; IIRC, это должно было работать вместе с предложением & move, но я не уверен, какова предполагаемая семантика этого.

Что ж, в соответствии с предложением варианта @ steven099 (которое я предпочел) большинство пользователей (на данный момент) использовали бы Pinned<T> , который содержит T по значению, но равен !Sized ( и, возможно, !DynSized ; точный дизайн иерархии признаков открыт для велосипедистов). Фактически это выглядит очень похоже на существующее предложение, за исключением того, что &'a mut Pinned<T> заменяет Pin<'a, T> . Но он более совместим с текущим и будущим кодом, который является общим для &mut T (для T: ?Sized ), и он более обратно совместим с будущим дизайном для истинных, собственных неподвижных типов, которым не нужно было бы используйте Pinned .

Чтобы углубиться в детали, Pinned может выглядеть так:

extern { type MakeMeUnsized; }

#[fundamental]
#[repr(C)]
struct Pinned<T> {
    val: T,
    _make_me_unsized: MakeMeUnsized,
}

Обычно вы не создаете Pinned<T> напрямую. Вместо этого вы можете преобразовать Foo<T> в Foo<Pinned<T>> , где Foo - это любой умный указатель, который гарантирует, что он не будет перемещать свое содержимое:

// This would actually be a method on Box:
fn pin_box<T>(b: Box<T>) -> Box<Pinned<T>> {
    unsafe { transmute(b) }
}

(Также может быть API закрепления стека, вероятно, основанный на макросе, но это немного сложнее реализовать.)

В этом примере FakeGenerator обозначает тип генератора, созданный компилятором:

enum FakeGenerator {
    Initial,
    SelfBorrowing { val: i32, reference_to_val: *const i32 },
    Finished,
}

После закрепления значения FakeGenerator пользовательский код больше не должен иметь доступ к нему напрямую по значению ( foo: FakeGenerator ) или даже по ссылке ( foo: &mut FakeGenerator ), поскольку последний будет разрешить использование swap или replace . Вместо этого код пользователя работает напрямую, например, с &mut Pinned<FakeGenerator> . Опять же, это очень похоже на правила для существующего предложения PinMut<'a, FakeGenerator> . Но в качестве примера лучшей компоновки, сгенерированный компилятором impl может использовать существующий трейт Iterator , вместо того, чтобы нуждаться в новом, который принимает Pin<Self> :

impl Iterator for Pinned<FakeGenerator> {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        /* elided */
    }
}

С другой стороны, для первоначального построения мы можем передать значения FakeGenerator напрямую и разрешить их перемещение, если мы гарантируем, что только состояния, не являющиеся самозаемыми, доступны до закрепления:

impl FakeGenerator {
    fn new() -> Self { FakeGenerator::Initial }
}

Итак, если мы хотим составить два FakeGenerator s по значению, конструкция проста:

struct TwoGenerators {
    a: FakeGenerator,
    b: FakeGenerator,
}

impl TwoGenerators {
    fn new() -> Self {
        TwoGenerators {
            a: FakeGenerator::new(),
            b: FakeGenerator::new(),
        }
    }
}

Затем мы можем закрепить объект TwoGenerators целиком:

let generator = pin_box(Box::new(TwoGenerators::new()));

Но, как вы упомянули, нам нужны проекции полей: способ перехода от &mut Pinned<TwoGenerators> к &mut Pinned<FakeGenerator> (доступ к a или b ). Здесь он тоже очень похож на существующий дизайн Pin . На данный момент мы будем использовать макрос для создания средств доступа:

// Some helper methods:
impl<T> Pinned<T> {
    // Only call this if you can promise not to call swap/replace/etc.:
    unsafe fn unpin_mut(&mut self) -> &mut T {
        &mut self.val
    }
    // Only call this if you promise not to call swap/replace/etc. on the
    // *input*, after the borrow is over.
    unsafe fn with_mut(ptr: &mut T) -> &mut Pinned<T> {
        &mut *(ptr as *mut T as *mut Pinned<T>)
    }
}

// These accessors would be macro-generated:
impl Pinned<TwoGenerators> {
    fn a(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().a) }
    }
    fn b(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().b) }
    }
}

Однако, как и в существующем дизайне, макрос должен предотвратить реализацию пользователем Drop для TwoGenerators . С другой стороны, в идеале компилятор позволит нам impl Drop for Pinned<TwoGenerators> . В настоящее время он отклоняет это с ошибкой, что «Реализации Drop не могут быть специализированными», но это можно изменить. ИМО, это было бы немного лучше, чем PinDrop , поскольку нам не понадобится новая черта.

Теперь, в качестве будущего расширения, в принципе, компилятор может поддерживать использование собственного синтаксиса поля для перехода от Pinned<Struct> к Pinned<Field> , подобно предложению в существующем дизайне, что компилятор может добавить собственный &pin T . Это может быть хорошей идеей, поскольку, условно говоря, не потребует большой работы по реализации.

Однако мой идеальный «эндшпиль» не в этом, а в чем-то более драматичном (и более сложном для реализации), когда компилятор когда-нибудь будет поддерживать «родные» неподвижные типы, включая экзистенциальные времена жизни. Что-то вроде:

struct SelfReferential {
    foo: i32,
    ref_to_foo: &'foo i32,
}

Эти типы будут вести себя как Pinned<T> , будучи !Sized и т. Д. [1] Однако, в отличие от Pinned , они не требуют начального подвижного состояния. Вместо этого они будут работать на основе «гарантированного исключения копий», когда компилятор позволит использовать неподвижные типы в возвращаемых значениях функций, структурных литералах и в различных других местах, но гарантирует, что внутри они будут построены на месте. Так что мы не только могли написать:

let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };

(что вы уже вроде как можете делать ) ... мы могли бы также написать такие вещи, как:

fn make_self_referential() -> SelfReferential {
    let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };
    sr
}

или же

let sr: Box<SelfReferential> = box SelfReferential { foo: 5, ref_to_foo: &sr.foo };

Конечно, приведенное выше - лишь очень приблизительный набросок того, как будет выглядеть эта функция; у синтаксиса, который я использовал, есть проблемы, и синтаксис будет наименьшей из многих сложностей, связанных с проектированием функции. (Тем не менее, я подумал об этом достаточно, чтобы вполне уверенно, что со сложностями можно справиться - это не просто бессвязная идея, которая не может работать.)

Я просто упоминаю это как часть мотивации, еще в настоящем, для использования дизайна !DynSized -ish вместо существующего дизайна Pin . Я думаю, что &'a Pinned<T> уже лучше, чем Pin<'a, T> потому что он позволяет избежать комбинаторного взрыва типов указателей. Но он наследует некоторые из тех же проблем:

  1. Он принципиально не поддерживает типы, у которых отсутствует начальное подвижное состояние, и
  2. Это шумно, сбивает с толку (разницу между закрепленными и незакрепленными ссылками на один и тот же тип трудно объяснить), и из-за этого неподвижные типы кажутся второсортными. Я хочу, чтобы неподвижные, ссылающиеся на себя типы чувствовали себя первоклассными - как потому, что они полезны по своей сути, так и чтобы Rust выглядел лучше для людей, пришедших с других языков, где создание самореференциальных значений легко и широко.

Я предвижу, что в будущем собственные неподвижные типы решат обе проблемы, и в большинстве кодов никогда не потребуется использовать слово «булавка». (Хотя Pinned все еще может иметь некоторые варианты использования, в случаях, когда исключение копирования недостаточно хорошо и вам действительно нужно начальное перемещаемое состояние.) Напротив, Pin запечатывает концепцию Отделяйте «еще не закрепленное» состояние в дизайне каждой черты, которая его использует. А встроенный &pin запечет его в языке.

В любом случае, вот ссылка на Pinned выше.

[1] ... хотя нам может потребоваться новая черта ReallySized (с менее глупым именем) для типов, размер которых статический, но может или не может быть подвижным. Дело в том, что здесь все равно будет некоторый отток, потому что с поддержкой безразмерных rvalue, многие функции, которые в настоящее время принимают аргументы Sized могут так же хорошо работать с неограниченными, но подвижными. Мы будем изменять как границы существующих функций, так и рекомендации относительно того, какие границы использовать для будущих функций. Я утверждаю, что в будущем выпуске, возможно, даже стоит изменить стандартную (подразумеваемую) привязку для универсальных функций, хотя это будет иметь некоторые недостатки.

[изменить: пример исправленного кода]

@RalfJung

Я рассматриваю API закрепления стека на основе замыкания как решение этой проблемы с будущим (однажды & pin является языковым примитивом) для чего-то более эргономичного и проверенного компилятором. Существует также это решение на основе макросов, предложенное @cramertj.

Я считаю, что метод, основанный на закрытии, не работает должным образом с async / await, потому что вы не можете уступить из-за закрытия. Тем не менее, макрос на основе макросов интересен; если это действительно безопасно, это довольно гениально. Сначала я не думал, что это может сработать, потому что паника во время одного из падений прицела может привести к утечке других, но, видимо, это было исправлено с помощью MIR?

Я также не был уверен в взаимодействии между гарантией «запуск деструкторов до освобождения памяти» и возможностью проецирования закрепленных полей; если бы выпадение верхнего уровня запаниковало, я думал, что спроецированные закрепленные поля в значении не будут выпадать. Однако на игровой площадке Rust действительно кажется, что поля этого типа все равно запускаются даже после паники типа верхнего уровня, что довольно интересно! Эта гарантия действительно где-либо задокументирована? Это кажется необходимым, если закрепление стека должно работать с проекциями булавок (это или что-то более тяжелое, например, PinDrop всегда прерывается при панике, что кажется нежелательным, поскольку это приведет к разнице в функциональности между Drop и PinDrop).

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

Я понимаю вашу точку зрения: кто-то мог полагаться на реализацию auto !Unpin из собственного поля Vec<T> и реализовывать свои собственные аксессоры, которые предполагали, что закрепление было транзитивным для некоторого конкретного !Unpin T . Хотя верно, что PinMut<Vec<T>> самом деле не предоставляет вам безопасного способа получить PinMut<T> , небезопасный код все же может использовать PinMut::deref для получения исходных указателей и делать предположения о указатели стабильны. Итак, я предполагаю, что это еще одна ситуация, когда он обратно совместим, только если небезопасный код не полагается на то, что !Unpin транзитивен через Vec (или что-то еще). Однако такая сильная зависимость от негативных рассуждений в любом случае кажется мне подозрительной; если вы хотите убедиться, что вы !Unpin где T не является, вы всегда можете добавить PhantomData<T> (я думаю, этот аргумент также применим к необработанным указателям). Публикации типа «это UB полагать, что типы в стандартной библиотеке есть! Открепить небезопасный код, независимо от их параметров типа, если только тип явно не отказывается от него или его документация не заявляет, что на него можно положиться», вероятно, будет достаточно.

Тем не менее, макрос на основе макросов интересен; если это действительно безопасно, это довольно гениально. Сначала я не думал, что это может сработать, потому что паника во время одного из падений прицела может привести к утечке других, но, видимо, это было исправлено с помощью MIR?

Это будет ошибкой в ​​MIR, если паника во время сброса приведет к пропуску удаления оставшихся локальных переменных. Это должно просто переключиться с «нормального падения» на «разматывающееся падение». Другая паника приведет к прерыванию программы.

@RalfJung

Это будет ошибкой в ​​MIR, если паника во время сброса приведет к пропуску удаления оставшихся локальных переменных. Это должно просто переключиться с «нормального падения» на «разматывающееся падение». Другая паника приведет к прерыванию программы.

Считаются ли другие поля в удаляемой структуре локальными переменными в этом контексте? Мне это действительно не ясно из любой пользовательской документации (на самом деле, это верно для всей гарантии, о которой вы говорите - я только узнал, что это действительно считалось ошибкой, которую необходимо было исправить с помощью средства отслеживания проблем).

@comex

Что касается Pinned (то, что вы предлагаете), я думаю, что если бы мы захотели его реализовать (как только в Rust были бы реализованы все функции), нам не пришлось бы делать намного больше, чтобы сделать это. обратно совместим с существующим кодом:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

(Я думаю, что также была предложена реализация deref от Pin до Pinned ). Поучительно посмотреть на те места, где кажется, что это не сработает. Например:

impl Drop for Pinned<TwoGenerators>

не работает (по крайней мере, не напрямую) с PinMut . Даже если мы предположим, что это заменяет Drop на сам TwoGenerators при создании типа Pinned (я не уверен, как это будет работать?), Rust все равно не будет знать, что нужно вызывать версию конструктора Pinned для любых полей, которые были спроецированы, поскольку поля будут храниться только по значению. Если закрепленная версия деструктора всегда использовалась, если она существовала, это фактически идентично PinDrop , только со странным синтаксисом, и я не вижу, чем это лучше.

Однако возникает соблазн подумать о выборе деструктора, рекурсивно проанализировав, было ли значение укоренено в Pinned . Заметьте, что если мы когда-либо захотим разрешить объекты черт по значению, мы не обязательно сможем решить, использовать ли отбрасывание Pinned<T> или T во время компиляции; Я полагаю, вы думаете, что в таких случаях может быть отдельная запись vtable для версии Pinned ? Эта идея меня как бы интригует. Это определенно требует существенной поддержки компилятора (гораздо больше, чем предлагаемый PinDrop ), но в некоторых отношениях это может быть лучше.

Однако, перечитывая поток, я вспоминаю, что есть и другие проблемы: реализация deref для закрепленных типов действительно не работает для Pinned<T> (я подозреваю, что это потому, что реализация deref на PinMut в некотором роде логически неверен, поэтому он продолжает вызывать проблемы, но действительно трудно оправдать его потерю, учитывая его удобство - если вы не создадите целую кучу типов безоговорочно Unpin все равно RefCell довольно тревожным, поскольку при наличии Pinned::deref это означает, что мы даже не сможем обеспечить динамическое закрепление с помощью существующего типа (я не знаю если будет достаточно специализации). Это также предполагает, что если мы сохраним реализацию deref , нам придется в конечном итоге дублировать поверхность API с помощью Pinned почти так же, как и с Pin ; и если мы его не сохраним, Pinned<T> станет невероятно трудным в использовании. Также не похоже, что Box<Pinned<T>> будет работать, если мы не добавим ?Move отдельно от ?DynSized (как указано в потоке).

Все это может быть хорошей идеей, но в контексте текущего Rust все это кажется мне несколько непривлекательным, особенно если учесть, что в основном ни один из методов в текущем Rust не имеет границ ?Move (имеется ввиду отсутствие deref действительно повредит, если только тип не Unpin в этом случае Pinned дает никаких преимуществ). Я подозреваю, что он моделирует то, что на самом деле происходит, более тщательно, закрепляя собственное свойство, что, в свою очередь, затрудняет принятие специальных решений, таких как PinMut::deref и делает интерфейс гораздо более приятным (во всяком случае, субъективно) , но он добавляет много языкового механизма для этого, и не похоже, что вы думаете, что это особенно полезно (по сравнению с собственными неподвижными типами, которые вы предлагаете). Я также не знаю, действительно ли то, что мы получаем, что мы не можем получить полностью обратно-совместимо (в основном, вызов другой реализации отбрасывания в зависимости от статуса вывода), действительно так полезно, даже если это можно сделать (возможно, вы могли бы сохранить ветвь в деструкторе иногда, если вы знали, что тип закреплен?). Поэтому я не уверен, стоит ли сейчас менять предложение PinMut . Но, возможно, мне не хватает действительно убедительного конкретного варианта использования.

Что касается Pinned (то, что вы предлагаете), я думаю, что если бы мы захотели его реализовать (как только в Rust были бы реализованы все функции), нам не пришлось бы делать намного больше, чтобы сделать это. обратно совместим с существующим кодом:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

Во-первых, для самого Pinned потребуется минимальная поддержка компилятора, если это то, что вы подразумеваете под «функциями». Если вы имеете в виду дизайн библиотеки, например DynSized и связанные с ним черты, тогда это верно, но ...

То, что вы предложили, на самом деле не будет иметь обратной совместимости, поскольку, например, вы можете попытаться реализовать одну и ту же черту как для Pin<'a, T> и для &'a T , что внезапно станет противоречивым.

В более общем плане существует значительная разница в дизайне API. С Pin трейты, которые, как ожидается, будут имплементированы неподвижными типами, должны иметь свои методы, принимающие PinMut<Self> , общие функции, которые хотят принимать ссылки на неподвижные типы, должны выглядеть как fn foo<T>(p: PinMut<T>) , и т. д. С другой стороны, дизайн Pinned в большинстве случаев позволяет избежать необходимости в новых чертах, поскольку вы можете внедрить черты для Pinned<MyStruct> . Таким образом:

  1. Неподвижные типы будут несовместимы с существующими признаками, методы которых принимают &self или &mut self : например, генераторы не могут имплементировать Iterator . Так что нам понадобится куча новых трейтов, которые эквивалентны существующим, но взамен возьмут PinMut<Self> . Если бы мы изменили курс и сделали PinMut<T> псевдонимом для &mut Pinned<T> , тогда мы могли бы вернуться назад и отказаться от всех этих повторяющихся свойств, но это было бы довольно глупо. Лучше вообще не нуждаться в дубликатах.

  2. С другой стороны, недавно разработанные или специфические для генератора черты, вероятно, потребуют PinMut<Self> в качестве единственного варианта за счет добавления шума для типов, которые хотят реализовать черты, но не являются неподвижными и не нуждаются в быть прикрепленным. (В частности, вызывающие абоненты должны будут вызвать PinMut::new чтобы перейти от &mut self к PinMut<Self> , при условии, что Self: Unpin .) Даже если Pin<T> станет псевдоним для &mut Pinned<T> , избавиться от этого шума не удастся. И будущие естественные неподвижные типы, которые я представляю, будут в той же ситуации, что и подвижные типы, без необходимости их нужно будет заключать в Pinned когда они всегда считаются закрепленными.

Я отвечу на остальную часть вашего сообщения во втором сообщении.

По поводу Drop

Меня немного смущает то, что вы говорите о Drop , но если вы пытаетесь сделать его обратно совместимым с PinMut , я не буду об этом думать потому что я не думаю, что это хороший подход.

Я думаю, что лучший подход состоит в том, что если у вас есть структура типа TwoGenerators , у вас есть два варианта:

  1. Никаких ручных Drop impl ни для TwoGenerators ни для Pinned<TwoGenerators> ;
  2. Вы подразумеваете Drop за Pinned<TwoGenerators> ; тем временем тот же макрос, который предоставляет вам средства доступа, сгенерирует Drop impl для самого TwoGenerators , который просто преобразует self в &mut Pinned<TwoGenerators> и отбрасывает его. (Это безопасно: инвариант, необходимый для преобразования &mut T в &mut Pinned<T> заключается в том, что вы не переместите T после окончания займа, а в случае Drop , у вас есть последний заем, который когда-либо будет создан для этого значения.)

Единственная причина иметь два варианта состоит в том, что, как упоминалось ранее, вы можете не захотеть, чтобы ваша структура подразумевала Drop потому что структуры, которые не подразумевают это, обрабатываются средством проверки заимствований более свободно.

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

Относительно RefCell

Я не думаю, что Pinned::deref должно существовать. Безопасных макросов сгенерированных полевых средств доступа должно быть достаточно; Я не понимаю, насколько это «невероятно сложно использовать». Это немного менее приятно, чем возможность использовать собственный синтаксис полей, но когда-нибудь это будет исправлено с помощью собственных неподвижных структур. В любом случае, если его сложно использовать, та же проблема относится к Pin .

Это позволяет избежать проблемы с RefCell .

особенно если учесть, что практически ни один из методов в текущем Rust не имеет границ ?Move (что означает, что отсутствие deref действительно повредит [..])

Напротив, все, что связано с ?Sized неявно является ?Move .

В этом есть смысл, потому что в целом код с ?Sized может предполагать подвижность. Единственное исключение - небезопасный код, который вызывает size_of_val а затем memcpy - это столько байтов, поэтому нам нужен хак, при котором size_of_val будет вызывать панику из-за неподвижных типов (и не рекомендуется в пользу новой функции. с правильной оценкой).

Меня немного смущает то, что вы говорите о Drop, но в той степени, в которой вы пытаетесь сделать его обратно совместимым с PinMut, я не буду об этом думать, потому что я не думаю, что это хороший подход .

Я говорил, что если что-то не имеет обратной совместимости с PinMut , для этого должна быть веская причина. Однако то, что вы предлагаете, функционально идентично PinDrop во всех деталях, за исключением того, что вы хотите реализовать его на Pinned<T> (что не работает в текущем Rust). Лично я считаю, что специализация Drop создает действительно сомнительный прецедент и почти наверняка нежелательна по причинам, не имеющим ничего общего с закреплением, поэтому я не считаю это внутренним преимуществом. В любом случае я думаю, что PinDrop можно в основном отделить от остальной части вашего предложения.

В любом случае, если его сложно использовать, та же проблема относится и к Pin.

Конечно, и если бы мы захотели избавиться от PinMut::deref , он также прекрасно сочетался бы с такими типами, как RefCell ; разница в том, что мы все еще можем сколотить решение с помощью PinMut при поддержке deref , что, похоже, не работает с Pinned . Если бы мы избавились от реализации deref , я бы с гораздо большей вероятностью согласился с тем, что Pinned дает значимое преимущество.

Но я действительно не уверен , я согласен , что не имея deref это просто небольшая проблема на практике: например, это означает , что в своем нынешнем состоянии вы ничего не можете с делать &Pinned<Vec<T>> , где T: !Unpin , и то же самое применимо практически ко всем существующим типам библиотек. Это проблема, проистекающая из способа работы Unpin , а не из-за конкретной ссылки, которая у вас есть. Экосистеме придется коллективно решить делать что-то вроде impl Deref for Pinned<Vec<T>> { type Target = Pinned<[T]>; } или чего-то в этом роде, что, я думаю, я согласен, было бы предпочтительнее impl PinDeref<Vec<T>> если его можно заставить работать, но это в мир без deref . В мире с deref почти все библиотеки могут обойтись без каких-либо аксессуаров, связанных с выводами, и по-прежнему имеют приличную поддержку для типов !Unpin .

Напротив, все, что имеет границу размера, неявно перемещается.

Ах да, это хороший момент. К сожалению, целый ряд кода Rust не работает с типами с привязкой !Sized , потому что это не значение по умолчанию, но, по крайней мере, некоторые из них работают. Я не думаю, что это убедительное преимущество, потому что большинство вещей, которые я могу делать с значениями без размера, - это вызов для них методов & или &mut (например, для срезов или объектов признаков), ни один из что я смогу сделать по вашему предложению (кроме типов Unpin ), поскольку вам не нужны Pinned::deref . Может быть, в общих случаях можно было бы справиться, заставив реализации #[derive] также генерировать отдельные экземпляры для Pinned<T> или чего-то еще?

Drop

Лично я считаю, что специализация Drop создает действительно сомнительный прецедент и почти наверняка нежелательна по причинам, не имеющим ничего общего с закреплением, поэтому я не считаю это внутренним преимуществом. В любом случае я думаю, что PinDrop можно в основном отделить от остальной части вашего предложения.

Я согласен, что это разделимо, но не думаю, что это сомнительно. По крайней мере ... то, что вы сказали, правильно, это форма специализации. Это не буквально специализируется на каком-то родительском бланке impl из Drop , но созданный компилятором drop glue выполняет эквивалент специализации, вызывая Drop только если он реализован. Реализация «пользовательского пространства» будет выглядеть так (без учета того факта, что вы не можете вручную вызвать drop ):

trait DropIfImplemented {
    fn maybe_drop(&mut self);
}
impl<T: ?Sized> DropIfImplemented for T {
    default fn maybe_drop(&mut self) {}
}
impl<T: ?Sized + Drop> DropIfImplemented for T {
    fn maybe_drop(&mut self) { self.drop() }
}

Итак, я полагаю, что причина, по которой вы в настоящее время не можете писать «специализированные» имплименты Drop, - это та же самая причина, по которой сама специализация в настоящее время является несостоятельной: несогласованность между trans (которая стирает параметры времени жизни) и typeck (чего нет). Другими словами, если бы мы могли написать, скажем, impl Drop for Foo<'static> , он фактически вызывался бы для любого Foo<'a> , а не только для Foo<'static> , потому что кодогенератор предполагает, что эти два типа идентичны.

Хорошая новость заключается в том, что, как вы, вероятно, знаете, были попытки найти способ ограничить специализированные имплсы, чтобы они не могли создать такого рода несогласованность. Ожидается, что специализация в конечном итоге появится с таким ограничением. Как только это произойдет, я не вижу причин, по которым мы не могли бы применить те же правила к Drop impls - и чтобы сделать язык максимально согласованным, мы должны это сделать.

Теперь мы не хотим блокировать привязку к специализации. Однако я утверждаю, что разрешение impl Drop for Pinned<MyStruct> - или, в более общем смысле, разрешение impl<params> Drop for Pinned<MyStruct<params>> при тех же условиях, которые компилятор в настоящее время допускает impl<params> Drop for MyStruct<params> - гарантированно будет подмножеством того, какая специализация allow, поэтому, если мы сделаем для него особый случай, он в конечном итоге превратится в более общее правило.

Но опять же, это отделимо; если людям это не нравится, у нас может быть отдельная черта.

Unpin

Но я действительно не уверен, что согласен с тем, что отсутствие deref - это всего лишь небольшая проблема на практике: например, это означает, что в текущем состоянии вы ничего не можете сделать с &Pinned<Vec<T>> where T: !Unpin , и то же самое применимо практически ко всем существующим типам библиотек. Это проблема, проистекающая из способа работы Unpin , а не из-за конкретной ссылки, которая у вас есть.

Э… ладно, позволь мне исправить свое утверждение. Pinned::deref должен существовать, но он должен быть ограничен Unpin - хотя я собираюсь называть это Move .

Единственная причина, по которой Deref impl для PinMut вызывает проблемы с RefCell заключается в том, что (в отличие от DerefMut impl) он не ограничен Unpin . Причина отсутствия привязки заключается в желании позволить пользователям получать &MyImmovableType , позволяя неподвижным типам внедрять признаки с методами, которые принимают &self , для передачи универсальным функциям, которые принимают &T и т. Д. Это принципиально невозможно для &mut self , но в основном работает с &self потому что вы не можете выйти из него с помощью mem::swap или mem::replace - то есть, кроме случаев, когда вы используете RefCell . Но рассуждают, что совместимость с существующими ссылками достаточно ценна, чтобы ее поддерживать, даже если ограничение неизменяемыми ссылками кажется произвольным, даже если оно вызывает путаницу.

С помощью Pinned мы можем поддерживать как неизменяемые, так и изменяемые ссылки: вы просто добавляете свои черты в Pinned<MyStruct> а не непосредственно в MyStruct . Обратной стороной является то, что он несовместим с чертами или функциями, которые принимают &T но имеют отдельную Self: Sized bound; но это относительно редко и часто непреднамеренно.

Интересно, что сама по себе Pinned самом деле вообще не требует существования Unpin . В конце концов, зачем кому-то на самом деле создавать &Pinned<Vec<T>> ? С PinMut различные черты будут принимать PinMut<Self> , так что даже имплименты этих черт для подвижных типов должны получить PinMut . С Pinned , как я уже сказал, черты по-прежнему будут принимать &self или &mut self , и вы будете имплантировать их для Pinned<MyStruct> . Если вы хотите внедрить ту же черту для Vec<T> , Pinned не нужно использовать.

Однако одним из потенциальных источников может быть макрос полевого доступа. Если у тебя есть

struct SomePinnable {
    gen: FakeGenerator,
    also_a_vec: Vec<Foo>,
}

тогда в простейшем случае аксессоры всегда будут генерироваться с использованием Pinned :

impl Pinned<SomePinnable> {
    fn gen(&self) -> &Pinned<FakeGenerator> { … }
    fn gen_mut(&mut self) -> &mut Pinned<FakeGenerator> { … }
    fn also_a_vec(&self) -> &Pinned<Vec<Foo>> { … }
    fn also_a_vec_mut(&mut self) -> &mut Pinned<Vec<Foo>> { … }
}

… И вам решать, что делать с Pinned если вы этого не хотите. На самом деле, я думаю, что это нормально, потому что Unpin / Move действительно должно существовать, см. Ниже. Но если бы его не было, альтернативой была бы возможность выбрать для каждого поля прямую ссылку, а не Pinned . То есть у вас было бы

    fn also_a_vec(&self) -> &Vec<Foo> { … }
    fn also_a_vec_mut(&mut self) -> &mut Vec<Foo> { … }

Было бы неправильно иметь и Pinned и средства доступа, отличные от Pinned , но любой из них сам по себе подойдет.

… Но да, нам действительно нужна черта Move , но не для Pinned . Например, это будет частью привязки для новой версии size_of_val (исправление: это не так, но ожидается, что небезопасный код проверит его перед попыткой memcpy произвольных типов на основе результата из size_of_val ); в будущем с неизмеренными rvalue это будет граница (ослабленная с Sized ) для ptr::read и mem::swap и mem::replace ; если мы когда-нибудь получим &move , это будет пределом для того, чтобы позволить вам, ну, выйти из одного; и нечто подобное применимо к гарантированной копии.

Итак, пока у нас есть черта, нет причин не иметь Pinned::derefderef_mut ) с привязкой T: Move .

[править: как мне напомнил pythonesque, семантика здесь отличается от Unpin , так что не беда.]

(А затем такие типы, как Vec и Box , захотят вручную имплантировать Move чтобы он применялся независимо от того, является ли тип элемента Move .)

Э… ладно, позволь мне исправить свое утверждение. Pinned :: deref должен существовать, но он должен быть ограничен на Unpin - хотя я собираюсь называть его Move.

Хорошо, подожди. Это ?Move или Move ? Первое означало бы, что во многих случаях !Unpin типы даже не могут быть созданы; последнее заставляет меня задаться вопросом, как именно мы имеем в виду такие типы, как Pinned<T> (поскольку ?DynSized самом деле не является для них правильной границей). Я, конечно, надеюсь, что это не одно и то же - в противном случае обращение с Move как Unpin снова делает именно то, чего мы пытаемся избежать, и делает тип неподвижным в момент его создания.

Обратной стороной является то, что он несовместим с чертами или функциями, которые принимают & T, но имеют отдельную привязку Self: Sized; но это относительно редко и часто непреднамеренно.

Есть гораздо более существенный практический недостаток, заключающийся в том, что некоторые из этих черт или функций действительно работают с & PinnedCегодня. Их можно заставить работать с этим, но для этого потребуется огромное количество дополнительных реализаций трейтов и (как я уже сказал), вероятно, значительный пересмотр существующих реализаций #[derive] . Это также затраты, которые придется оплачивать и за новые вещи - вам также придется реализовать все за &Pinned<Self> если вы хотите, чтобы это работало с типами !Unpin . Это (намного) лучшая ситуация для признаков, которые принимают &mut self чем для PinMut , но хуже для &self , что (я подозреваю) гораздо более распространено. Поэтому я говорю, что считаю это более правильным решением (в том смысле, что если бы у нас не было многих существующих библиотек Rust, версия Pinned была бы лучше), но, возможно, не более удобное.

С закрепленным, как я уже сказал, черты будут продолжать принимать & self или & mut self, и вы будете использовать их для закрепленного

Повторная реализация каждой черты в поверхности API Vec , только с Pinned этот раз, для меня не так уж хороша (тем более, что некоторые черты даже не работают с ней). Я почти уверен, что либо выборочно реализую Deref в каждом конкретном случае (например, позволяю &Pinned<Vec<T>> перейти к &[Pinned<T>] ), либо просто позволяю всем Vec be Unpin (без проецирования булавки) более разумно. В любом случае и то, и другое - это больше работы, чем абсолютно ничего не делать, и это должно быть воспроизведено по целому ряду существующих типов и свойств, чтобы неподвижный объект работал с ними.

Я мог быть уверен в обратном - мне действительно нравится решение Pinned чем больше я думаю об этом, - но я просто не знаю, где на самом деле все эти новые реализации трейтов на Pinned<T> собирается прийти из; мне кажется более вероятным, что люди просто не потрудятся их реализовать.

Интересно, что сам по себе Pinned вообще не требует существования Unpin. В конце концов, зачем кому-то на самом деле создавать & Закрепленный>?

Есть довольно веские причины для этого (например: в закрепленном типе есть Vec ). Навязчивые коллекции будут очень часто сталкиваться с подобным сценарием. Я думаю, что любое предложение, основанное на идее о том, что людям никогда не понадобятся ссылки Pinned на существующие контейнеры или что вы должны согласиться, чтобы Unpin работал, вряд ли сработает. Невозможность в основном выбрать обычную экосистему Rust путем добавления ограничения Unpin будет невероятно разрушительным (фактически, почти каждый вариант использования, который у меня есть для неподвижных типов, станет значительно сложнее).

С PinMut различные черты будут принимать PinMut, поэтому даже имплиты этих черт для подвижных типов должны получить PinMut.

Конечно! Большим преимуществом версии Pinned является то, что вам не нужны отдельные черты для изменяемых закрепленных ссылок. Однако он хуже или нейтральнее, чем PinMut с deref почти для любого другого сценария.

Было бы неразумно иметь как закрепленные, так и не закрепленные аксессоры, но любой из них сам по себе подойдет.

Ручные средства доступа, требующие для реализации небезопасного кода, кажутся мне плохой идеей; Я не понимаю, как такое предложение могло бы обеспечить безопасность генерации аксессуаров (как вы помешаете кому-то предоставлять не закрепленные аксессоры, не заставляя их писать unsafe чтобы утверждать, что они этого не сделают?). Однако, как вы заметили, использование Move (при условии, что это действительно означает Unpin ) будет работать нормально.

Итак, пока у нас есть черта, нет причин не иметь Pinned :: deref (и deref_mut) с привязкой T: Move.

Конечно. Я конкретно говорю здесь о типах !Unpin . Типы Unpin также не имеют проблем с композицией с PinMut , поэтому с моей точки зрения они не так важны. Однако границы Unpin (или Move ) для дженериков неприятны, и в идеале вы должны иметь возможность избегать их везде, где это возможно. Опять же, как я уже сказал ранее: Unpin и все, что подразумевается под !Sized , не одно и то же, и я не думаю, что вы можете рассматривать их как одно и то же.

… Но да, нам нужно иметь черту Move, только не для Pinned. Например, это будет частью привязки к новой версии size_of_val; в будущем с неизмеренными rvalue это будет граница (ослабленная от Sized) для ptr :: read и mem :: swap и mem :: replace; если мы когда-нибудь переедем и переедем, это будет пределом для того, чтобы позволить вам, ну, переехать из одного места; и нечто подобное применимо к гарантированной копии.

Я думаю, что это еще раз объединяет !Unpin (что говорит о том, что тип может иметь инвариант закрепления не по умолчанию) и !DynSized -like !Move ; они не могут быть одинаковыми, не вызывая нежелательного замораживания.

Ой, ты совершенно прав. Unpin не может совпадать с Move .

Итак, я думаю, что снова верю в то, что Unpin и, следовательно, Pinned::deref не должно существовать вообще, и вместо этого мы должны избегать любых ситуаций (например, с макросом, генерирующим аксессоры), где вы получить тип вроде &Pinned<MovableType> . Но, возможно, есть аргумент, что это должно существовать как отдельная черта.

Повторная реализация каждой черты в поверхности API Vec , только с Pinned этот раз, звучит для меня не очень хорошо (тем более, что некоторые черты даже не работают с ней). Я почти уверен, что либо выборочно реализую Deref в каждом конкретном случае (например, позволяя &Pinned<Vec<T>> перейти к &[Pinned<T>] ), либо просто позволяю Vec целиком Unpin (без проецирования булавки) более разумно.

Да, я определенно не имел в виду предлагать переопределить всю поверхность API Vec или что-то в этом роде.

Я согласен с тем, что было бы неплохо запретить "проекцию булавки" на Vec , поскольку &Pinned<Vec<T>> кажется посторонним инвариантом - вам должно быть разрешено перемещать Vec без аннулирование булавки содержимого.

В качестве альтернативы, как насчет разрешения преобразования из Vec<T> в Vec<Pinned<T>> , который будет иметь большую часть Vec API, но опустить методы, которые могут вызвать перераспределение? То есть измените определение Vec с текущего struct Vec<T> на struct Vec<T: ?Sized + ActuallySized> , для менее глупого имени, где в основном Sized становится псевдонимом для ActuallySized + Move ; затем добавьте Sized привязанный к методам, которые могут вызвать перераспределение, и метод для преобразования.

Также необходимо изменить границу типа элемента встроенного типа среза с Sized на ActuallySized . Размышления об этом действительно напоминают мне о неловкости изменения Sized на что-то иное, чем Sized , но, с другой стороны, все еще верно, что подавляющее большинство Sized границы в существующем коде по сути требует Move . Необходимость знать size_of::<T>() для индексации в срез - это немного исключение ...

Конечно! Большим преимуществом версии Pinned является то, что вам не нужны отдельные черты для изменяемых закрепленных ссылок. Однако он хуже или нейтральнее, чем PinMut с deref почти для любого другого сценария.

Он также имеет то преимущество, что позволяет избежать конфликта с RefCell , по общему признанию, за счет конфликта с другими вещами (границы Sized ).

Ручные средства доступа, требующие для реализации небезопасного кода, кажутся мне плохой идеей; Я не понимаю, как такое предложение могло бы обеспечить безопасность генерации средств доступа (как вы можете помешать кому-либо предоставлять не закрепленные средства доступа, не заставляя их писать небезопасно, чтобы утверждать, что они этого не сделают?).

Поскольку аксессоры подразумеваются в Pinned<MyStruct> , а не в MyStruct напрямую. Если у вас есть &mut MyStruct , вы всегда можете вручную получить доступ к полю, чтобы получить &mut MyField , но вы все еще находитесь в подвижном состоянии. Если у вас есть &mut Pinned<MyStruct> , вы не можете получить &mut MyStruct (при условии, что MyStruct равно !Unpin или Unpin не существует), поэтому вам нужно использовать аксессор, чтобы добраться до полей. Аксессор принимает &mut Pinned<MyStruct> (т. Е. Принимает &mut self и применяется к Pinned<MyStruct> ) и дает вам либо &mut Pinned<MyField> или &mut MyField , в зависимости от того, какой вариант вы выбрали. Но у вас может быть только один тип доступа, так как критический инвариант заключается в том, что вы не должны иметь возможность получить &mut Pinned<MyField> , записать в него, освободить заем, а затем получить &mut MyField (и переместите его).

Есть довольно веские причины для этого (например: в закрепленном типе есть Vec ). Навязчивые коллекции будут очень часто сталкиваться с подобным сценарием. Я думаю, что любое предложение, основанное на идее о том, что людям никогда не понадобятся ссылки Pinned на существующие контейнеры или что вы должны согласиться, чтобы Unpin работал, вряд ли сработает. Невозможность в основном выбрать обычную экосистему Rust путем добавления ограничения Unpin будет невероятно разрушительным (фактически, почти каждый вариант использования, который у меня есть для неподвижных типов, станет значительно сложнее).

Я не совсем понимаю, что вы здесь имеете в виду, но это может быть потому, что вы отреагировали на мою ошибку, написав Unpin против Move :)

Теперь, когда я исправился ... Если Unpin существует, то Vec должно подразумевать это. Но предположим, что этого не существует, о каких именно сценариях вы говорите?

Для структуры, которая имеет Vec качестве одного из полей, я объяснил выше, как вы можете получить незакрепленную ссылку на поле (ценой невозможности получить закрепленную ссылку на нее, которая Это хорошо).

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

@comex

В качестве альтернативы, как насчет разрешения трансмутации из Vecв Vec>, который будет иметь большую часть Vec API, но опустить методы, которые могут вызвать перераспределение?

Потому как...

То есть изменить определение Vec из текущей структуры Vecструктурировать Vec, для менее глупого имени, где в основном Sized становится псевдонимом для ActuallySized + Move; затем добавьте привязку Sized к методам, которые могут вызвать перераспределение, и метод для преобразования.

... это звучит очень, очень сложно и на самом деле не делает того, что вы хотите (то есть получить обычный Vec<T> из &mut Pinned<Vec<T>> или чего-то еще). Это своего рода прохлады , что позволяет закрепить вектор постфактум, так что вы получите хороший аналог Box<Pinned<T>> , но это ортогональная беспокойство; это просто еще одна иллюстрация того факта, что закрепление, являющееся свойством принадлежащего типа, вероятно, правильно. Я думаю, что все в Unpin почти полностью не связано с вопросом о том, как создается ссылочный тип.

Теперь, когда я исправился ... Если Unpin существует, то Vec должен его задействовать. Но предположим, что этого не существует, о каких именно сценариях вы говорите?

Я просто отвечу на это, потому что думаю, это проиллюстрирует мою точку зрения: я не могу даже изменить поле i32 через PinMut без Unpin . В какой-то момент, если вы хотите что- то Pinned<MyType> кажется действительно раздражающей, особенно если данное поле всегда можно безопасно перемещать. Также кажется, что использование этого подхода со встроенным типом Pinned будет действительно запутанным, поскольку юридические проекции будут каким-то образом варьироваться в зависимости от поля независимо от типа поля, что уже было отклонено в Rust. когда были удалены поля mut (и ИМО, если мы собираемся добавить такую ​​аннотацию, unsafe - лучший выбор, так как на практике небезопасные поля - это огромная подушка). Поскольку встроенные закрепленные типы - это чуть ли не единственный способ сделать закрепленные перечисления удобными в использовании, я действительно забочусь о том, чтобы они могли быть в некоторой степени согласованными с остальным языком.

Но что более важно ...

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

Это в значительной степени убийственный вариант использования Unpin , и (для меня) тот факт, что он действительно работает, довольно фантастичен и подтверждает всю модель закрепления (до такой степени, что я думаю, даже если бы мы начинали с до Rust 1.0 мы, вероятно, хотели бы оставить Unpin как есть). Общие параметры, встроенные в структуру, также являются практически единственным случаем, когда вам нужно связать Unpin в текущих планах (чтобы почти все безопасные ссылочные типы безоговорочно реализуют Unpin ) проходить через.

Но на самом деле я не понимаю: почему вы хотите удалить Unpin ? Устранение этого практически ничего не дает вам; все приятные вещи, которые вы получаете от Pinned сверх PinMut (или наоборот), в значительной степени не связаны с его присутствием. Его добавление обеспечивает легкую совместимость с остальной частью экосистемы Rust. FWIW, Unpin и безусловная реализация deref на Pin также в значительной степени не связаны друг с другом, за исключением того, что без Unpin все типы чувствуют себя одинаково боль, которую причиняют типы !Unpin (что, вероятно, сделает безусловную реализацию deref более полезной). Я не могу помочь, но чувствую, что что-то упускаю.

Считаются ли другие поля в удаляемой структуре локальными переменными в этом контексте? Мне это действительно непонятно из любой пользовательской документации.

Честно говоря, я открыл https://github.com/rust-lang/rust/issues/50765, чтобы отследить это.


@pythonesque

В частности, я нахожу пример RefCell довольно тревожным, поскольку в присутствии Pinned :: deref это означает, что мы даже не сможем обеспечить динамическое закрепление с помощью существующего типа (я не знаю, будет ли достаточно специализации). Это также предполагает, что если мы сохраним реализацию deref, нам придется в конечном итоге дублировать поверхность API с помощью Pinned почти так же, как и с Pin; и если мы не сохраним его, прикрепленныйстановится невероятно трудным в использовании.

Решением для RefCell является предоставление дополнительных методов borrow_pin и borrow_pin_mut (с использованием Pin<RefCell<T>> ) и отслеживание закрепленного состояния внутренней части RefCell во время выполнения. Это должно работать как для PinMut и для Pinned . Итак, ваш аргумент здесь, что Pinned не помогает? Это не должно ухудшить положение и для RefCell .

Но потом ты пишешь

разница в том, что мы все еще можем сколотить решение с PinMut, поддерживая deref, который, похоже, не работает с Pinned

и я не знаю, что вы имеете в виду, почему это не должно работать с Pinned ?

@comex

Я не думаю, что Pinned :: deref должен существовать. Безопасных макросов сгенерированных полевых средств доступа должно быть достаточно; Я не понимаю, насколько это «невероятно сложно использовать».

Я не вижу связи между deref и полевыми средствами доступа. Но я также не вижу, как deref становится более проблематичным с Pinned<T> , поэтому я думаю, что сначала я дождусь ответа на свой вопрос выше.

Как и @pythonesque, я считаю, что отслеживание закрепленного состояния на типе,

Если мы собираемся сознательно придерживаться подхода, который, по нашему мнению, является менее «фундаментально правильным», мы должны хотя бы оставить себе много времени для экспериментов, прежде чем стабилизировать его, чтобы мы могли быть настолько уверены, насколько это возможно, в том, что мы не собираюсь закончить сожаление об этом.

@pythonesque , вау, большое спасибо за обширное резюме! Рад слышать, что есть RFC в разработке. :)

Сохраняется вызов функции, которая принимает изменяемую ссылку на закрепленное значение, если эта функция не использует что-то вроде mem::swap или mem::replace . Из-за этого кажется более естественным, что эти функции используют привязку Unpin чем небезопасно каждое изменяемое deref от Pin до значения Unpin .

Если функция позже будет обновлена ​​для использования swap , уже будет небезопасно вызывать ее по изменяемой ссылке на закрепленное значение. Когда swap и replace имеют эту привязку, обновленная функция должна делать то же самое, что делает более очевидным, что это изменение не является обратно совместимым.

Итак, некоторые мысли у меня были:

  1. По сути, Drop предоставляет те же привилегии, что и Unpin - вы можете получить &mut для чего-то, что ранее было в PinMut . А Drop безопасен, что означает, что Unpin должен быть безопасным (это mem::forget и снова утечкапокалипсиса).
  2. Это приятно, потому что это означает, что такие вещи, как текущие API на основе фьючерсов, которые не обрабатывают генераторы открепления, на 100% безопасны для реализации, даже если они принимают self: PinMut<Self> (нет unsafe impl Unpin ).
  3. Работает ли API звук, если Unpin безопасно? Ответ - да: пока генераторы не реализуют Unpin и привязать проект к типу !Unpin небезопасно, все в безопасности.
  4. Но это означает, что выступы штифтов небезопасны! Не идеально.
  5. Проекции булавок безопасны, если Self не реализует Unpin или Drop [edit: true или false?] Можем ли мы автоматизировать эту проверку?

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


еще одно замечание, потому что я все время забываю:

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

@withoutboats FWIW, это в точности соответствует выводам, к которым мы с @cramertj пришли в предыдущем раунде обсуждений. И я считаю, что мы можем автоматизировать проверку взаимных исключений, первоначально с помощью некоторых специально созданных атрибутов, генерируемых производным.

@withoutboats

По сути, Drop предоставляет те же привилегии, что и Unpin - вы можете получить & mut к чему-то, что ранее было в PinMut. И Drop безопасен, что означает, что Unpin должен быть безопасным (это снова mem :: Forgot и Leakpocalypse).

Я не вижу связи с Leakpocalypse, но согласен с другим. Единственная причина, по которой я (был?) Немного колеблюсь, заключается в том, что пока это всего лишь Drop , для меня это больше походило на крайний случай, о котором не многие люди должны заботиться. Не уверен, что это преимущество или нет. В любом случае, безопасный Unpin не только увеличивает здесь согласованность, но и «решает» проблему Drop , больше не делая ее особым случаем (вместо этого мы можем рассматривать каждый impl Drop как идёт с неявным impl Unpin ); Судя по тому, что вы говорите, это также упрощает использование на стороне Future . Так что, похоже, это общая победа.

@pythonesque, если я чего-то не Unpin тоже не вызывает никаких новых проблем для навязчивых коллекций, не так ли? Если они сработали, несмотря на безопасные Drop , они все равно должны работать.


@withoutboats

Проекции булавок безопасны, если Self не реализует Unpin или Drop [edit: true или false?] Можем ли мы автоматизировать эту проверку?

Сначала вы также упомянули здесь тип поля, и я собирался спросить, почему вы считаете это актуальным. Теперь я вижу, как вы редактируете сообщение. :) Согласен, что речь идет только о типе Self . Далее я в значительной степени повторю ваши аргументы в своих терминах.

По сути, возникает вопрос: как Self выбирает свой инвариант закрепления? По умолчанию мы предполагаем (даже если есть небезопасный код!), Что инвариант закрепления точно такой же, как и собственный инвариант, т. Е. Инвариант не зависит от местоположения, и этот тип не выполняет закрепление. Пока я не могу ничего сделать с PinMut<T> кроме как превратить его в &mut T , это безопасное предположение.

Чтобы включить проекции полей, инвариант закрепления должен быть вместо этого «все мои поля закреплены на инварианте соответствующего типа». Этот инвариант легко оправдывает закрепление проекций независимо от типов поля (т.е. они могут выбирать любой инвариант закрепления, который они хотят). Конечно, этот инвариант несовместим с превращением PinMut<T> в &mut T , поэтому нам лучше убедиться, что такие типы не являются Drop или Unpin .

Я не вижу связи с Leakpocalypse, но согласен с другим.

просто аналогия - Unpin - это Drop, как mem :: Forgot для Rc циклов. mem :: Forgot изначально была помечена как небезопасная, но для этого не было никаких оснований. (И тот же аргумент, что циклы Rc являются крайним случаем, был выдвинут против маркировки mem :: Forgot Safe.)

Копируя (духовно) из Discord, я действительно хотел бы увидеть доказательства того, что мы не просто перевернули проблему: сделав Unpin безопасным для реализации, сделав небезопасными для реализации структурные средства доступа к контактам (это также будет верно для любых небезопасный трейт, верно? Вам все равно придется писать небезопасный код). Это меня раздражает, потому что в подавляющем большинстве случаев они полностью безопасны - в основном, до тех пор, пока нет явного имплиентации Unpin для типа, точно так же, как мы всегда в безопасности, если для этого типа нет Drop impl. С текущим планом нам требуется что-то гораздо более сильное - должно быть явное! Открепить impl для типа - что будет верно для гораздо меньшего количества типов (это почти все, что мы можем сделать в библиотеке).

К сожалению, я понятия не имею, как и может ли компилятор проверить, есть ли ручное открепление impl для определенного типа или нет, в отличие от «has any impl», и я не уверен, плохо ли он взаимодействует со специализацией. Если бы у нас был какой-то определенный способ выполнить эту проверку, такой, что создателю типа не нужно писать небезопасный код, чтобы получить структурное закрепление, я был бы намного счастливее, если бы impl Unpin был безопасным, я думаю ... кажется ли это возможным?

У меня есть простой вопрос, который я пытаюсь понять сейчас. В дженериках открепление будет неявной привязкой, как размер, если ваш API не для всех общих параметров?

это должно быть правильно, чтобы vecчтобы оставаться в безопасности.

открепит будет неявная привязка, как размер

Нет.

это должно быть правильно, чтобы vec оставался безопасным.

Почему вы так думаете?

Раздражающая вещь, которую сегодня поразил PinMut::get_mut дает вам &mut со временем жизни исходного PinMut , но нет безопасного способа сделать то же самое, поскольку DerefMut дает вам заем со временем жизни изменяемой ссылки на PinMut . Возможно, нам стоит сделать get_mut безопасным и добавить get_mut_unchecked ?

Вы имеете в виду это?

fn get_mut(this: PinMut<'a, T>) -> &'a mut T where T : Unpin

Да, нам это нужно.

@RalfJung Совершенно верно .

Метод из ящика фьючерсов, в котором я хотел бы это использовать, выглядит так:

fn next(&mut self) -> Option<&'a mut F> {
    self.0.next().map(|f| unsafe { PinMut::get_mut(f) })
}

Мне пришлось использовать небезопасный блок, хотя F Unpin привязан к PinMut .

Ага, это просто упущение в API.

Вы хотите подготовить PR или я должен?

Я могу сделать это. Хотим называть это get_mut и get_mut_unchecked ?

Я добавлю безопасный map и также переименую текущий в map_unchecked . В основном для последовательности. Тогда все небезопасные функции PinMut заканчиваются на _unchecked и имеют безопасный эквивалент

Мы хотим называть это get_mut и get_mut_unchecked?

Звучит разумно.

Я добавлю сейф map

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

Осторожно. Как вы хотите, чтобы это выглядело?

Вы правы. Для возвращаемого значения требуется привязка Unpin .

У меня просто возникла идея: как насчет PinArc ? Мы могли бы использовать такую ​​вещь в BiLock impl (https://github.com/rust-lang-nursery/futures-rs/pull/1044) в ящике фьючерсов.

@MajorBreakfast PinArcPinRc и т. Д.) Зависит от Pin (не Mut версии). Я был бы готов добавить его, но я не был уверен, есть ли консенсус относительно того, какие именно гарантии он предлагает.

Я больше не уверен, что добавление PinArc что-то добавляет ^^ ' Arc уже не позволяет "переместить заимствованное содержимое"

@MajorBreakfast Вы можете выйти из Arc используя Arc::try_unwrap . Удаление этого или введение ограничения T: Unpin было бы обратно несовместимым изменением.

Привет!
Я немного подумал о том, как обеспечить безопасную работу аксессуаров для контактов. Насколько я понимаю, проблема с аксессуарами контактов возникает _только_, когда у вас есть комбинация T: !Unpin + Drop . С этой целью я видел некоторые дискуссии, пытающиеся предотвратить возможность существования такого типа T - например, делая признаки !Unpin и Drop взаимоисключающими различными способами, но там не было четкого способа сделать это без нарушения обратной совместимости. Рассматривая эту проблему более внимательно, мы можем получить безопасные аксессоры Pin, не препятствуя тому, чтобы тип был !Unpin и Drop , просто такой тип не мог быть помещен в Pin<T> . Предотвратить это_ - это то, что я считаю гораздо более выполнимым, и больше в духе того, как черта Unpin любом случае работает.

В настоящее время у меня есть «решение» для предотвращения безопасного ввода в тип Pin<T> который одновременно является !Unpin и Drop . Для этого требуются функции, которых у нас еще нет в ржавчине, что является проблемой, но я надеюсь, что это начало. В любом случае вот код ...

/// This is an empty trait, it is used solely in the bounds of `Pin`
unsafe trait Pinnable {}

/// Add the extra trait bounds here, and add it to the various impls of Pin as well
struct Pin<T: Pinnable> {
    ...
}

/// Then we impl Pinnable for all the types we want to be able to put into pins
unsafe impl<T: Unpin> Pinnable for T {}
unsafe impl<T: !Unpin + !Drop> Pinnable for T {}

Как типы Unpin , так и типы !Unpin но также и !Drop , не имеют проблем (насколько мне известно) с аксессуарами вывода. Это не нарушает какой-либо существующий код, который использует Drop , он просто ограничивает то, что может быть помещено в структуру Pin . В маловероятном * случае, когда кому-то нужен тип, который должен быть одновременно !Unpin и Drop (и может быть фактически помещен в Pin ), он сможет unsafe impl Pinnable за свой тип.

* У меня нет опыта работы с Pin , но я надеюсь, что большинство случаев, когда кому-то нужно impl Drop для типа !Unpin , не вызваны то, что нужно отбросить, по своей сути является !Unpin , а скорее вызвано структурой, имеющей поля Drop и !Unpin . В этом случае поля Drop могут быть разделены на их собственную структуру, которая не содержит полей !Unpin . При необходимости я могу более подробно остановиться на этом, потому что мне кажется, что это объяснение было изворотливым, но оно не предназначалось для того, чтобы быть основной частью комментария, поэтому я пока оставлю его кратким.

Насколько я понимаю, проблема с аксессуарами вывода возникает только тогда, когда у вас есть комбинация T:! Unpin + Drop.

Это больше похоже на «или», чем на «и».

Аксессоры закрепления подразумевают «структурную» интерпретацию закрепления: когда T закреплен, то же самое происходит и со всеми его полями. impl Unpin и Drop , с другой стороны, безопасны, потому что они предполагают, что тип вообще не заботится о закреплении - закрепление T не имеет значения; в частности, поля T ни в том, ни в другом случае не закрепляются.

Однако вы правильно поняли это позже, когда сказали, что типы !Unpin + !Drop - это те, для которых безопасно добавлять аксессоры. (Тип по-прежнему должен быть осторожен, чтобы не сделать ничего неправильного в своем небезопасном коде, но Unpin ad Drop - это два безопасных способа сломать закрепление аксессоров.)

Посмотрев на эту проблему более внимательно, мы можем получить безопасные аксессоры Pin, не препятствуя созданию типа! Открепите и отпустите, просто такой тип не может быть помещен в Pin.

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

Это больше похоже на «или», чем на «и».

Давайте пока просто поговорим об этом, потому что вся моя идея была основана на противоположном этому, поэтому, если я неправильно понял это, остальная идея не поможет.

Насколько я понимаю (в настоящее время), средства доступа к контактам с типом Unpin полностью исправны, независимо от сброса, поскольку Pin<T> &mut T любом случае фактически означает !Drop полностью исправны, даже для типа !Unpin , поскольку drop - единственная функция, которая может получить &mut self на типе !Unpin когда он входит в Pin , поэтому ничто не сможет сдвинуть его.

Если я допустил ошибку, дайте мне знать. Но если нет, то типы Unpin + Drop или !Unpin + !Drop будут полностью совместимы с аксессуарами вывода, и единственными типами проблем будут те, которые относятся к !Unpin + Drop .

@ashfordneil

Кроме того, средства доступа к булавкам для типа! Drop полностью исправны, даже для типа! Unpin, поскольку drop - единственная функция, которая может получить & mut self для типа! Unpin после того, как он входит в Pin, поэтому ничто не сможет убирать вещи.

Если я напишу struct Foo(InnerNotUnpin); impl Unpin for Foo {} тогда переход от PinMut<Foo> к PinMut<InnerNotUnpin> . Чтобы проекция была правильной, вы должны (а) запретить drop impls и (b) убедиться, что структура имеет вид Unpin только тогда, когда поле равно Unpin .

(а) следует просто запретить дроп импов на вещи, которые !Unpin правильно?
(б) следует учитывать тот факт, что реализация Unpin небезопасна - конечно, можно выстрелить себе в ногу, небезопасно заявив, что тип можно открепить, хотя на самом деле это невозможно - но так же возможно пометить что-то вроде Sync когда этого не должно быть, и в конечном итоге ведет к ненадлежащему поведению

(а) следует просто запретить дроп импов на то, что есть! Открепить, верно?

Да, но это невозможно по причинам обратной совместимости.

И это доходит до моей точки - запретить отбрасывать имплименты на уже существующие! Открепить невозможно по причинам обратной совместимости. Но нам не нужно предотвращать выпадение импликаций на уже существующие! Маркер! Unpin на типе не имеет смысла и не дает никаких обещаний, пока этот тип не окажется внутри Pin, поэтому перетаскивание, имплантированное на тип! Unpin, полностью корректно, пока этот тип не находится внутри Pin_. Я предлагаю изменить Pin с Pin<T> на Pin<T: Pinnable> где черта Pinnable действует как привратник для предотвращения типов, которые являются Drop + !Unpin (или более как правило, типы, для которых сопутствующие устройства проблематичны) не помещаются в Pin .

В этот момент вы делаете черту Unpin совершенно бесполезной, не так ли? Кроме того, вы все еще не можете выразить необходимые границы с текущим Rust. Если бы мы могли сказать « Unpin и Drop несовместимы», не было бы проблем. Кажется, вам нужно что-то подобное с Pinnable . Не понимаю, как это помогает.

Черта Unpin - сама по себе - уже «бесполезна». Признак _only_ означает, что после закрепления типа тип можно перемещать до того, как он войдет в закрепление.

@RalfJung и я только что встретились, чтобы обсудить эти API, и мы согласились, что мы готовы стабилизировать их без каких-либо серьезных изменений API.

Решение нерешенных вопросов

Я считаю, что API контактов:

  1. Основные концепции API PinMut и Unpin составляют наилучшее возможное решение, которое не включает встроенную поддержку языка.
  2. Эти API-интерфейсы не исключают встроенной поддержки языков в будущем, если мы сочтем это необходимым.

Однако есть несколько нерешенных вопросов относительно их ограничений и компромиссов, по которым я хочу зафиксировать наше решение.

Проблема Drop

Одна проблема, которую мы не обнаружили до тех пор, пока мы не объединили RFC, была проблема свойства Drop . Метод Drop::drop имеет API, который принимает self &mut self . По сути, это "открепление" Self , что нарушает гарантии, которые должны обеспечивать закрепленные типы.

К счастью, это не влияет на надежность основных типов «неподвижных генераторов», которые мы пытаемся проецировать с помощью пина: они не реализуют деструктор, поэтому их реализация !Unpin самом деле означает это .

Однако это означает, что для типов, которые вы определяете самостоятельно, обязательно безопасно «открепить» их, все, что вам нужно сделать, это реализовать Drop . Это означает, что Unpin будучи unsafe trait не дает нам дополнительной безопасности: реализация Drop так же хороша, как реализация Unpin насколько это разумно. обеспокоен.

По этой причине мы сделали трейт Unpin безопасным для реализации.

Булавочная проекция

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

Учитывая тип T с полем a с типом U , я могу безопасно "проецировать" из PinMut<'a, T> на PinMut<'a, U> , получив доступ поле a .

Или в коде:

struct Foo {
    bar: Bar,
}

impl Foo {
    fn bar(self: PinMut<'_, Foo>) -> PinMut<'_, Bar> {
        unsafe { PinMut::map_unchecked(self, |foo| &mut foo.bar) }
    }
}

На данный момент, если тип поля не реализует Unpin у rustc нет возможности _доказать_, что это безопасно. То есть для реализации такого метода проецирования в настоящее время требуется небезопасный код. К счастью, вы можете доказать, что это безопасно. Если тип поля не может реализовывать Unpin , это безопасно только в том случае, если все это выполняется:

  1. Тип Self не реализует Unpin или только условно реализует Unpin когда это реализует тип поля.
  2. Тип Self не реализует Drop , иначе деструктор для типа Self никогда ничего не перемещает из этого поля.

С помощью языковых расширений мы могли бы заставить компилятор проверить выполнение некоторых из этих условий (например, что Self не реализует Drop а только условно реализует Unpin ).

Из-за проблемы Drop , стабильной черты, эта проблема предшествовала любому решению, которое мы могли принять по этой проблеме, и без изменения языка невозможно сделать автоматическое проецирование булавок безопасным. Когда-нибудь мы можем внести эти изменения, но мы не будем блокировать стабилизацию этих API.

Указание гарантии «пин-код»

В конце процесса RFC @cramertj осознал, что с небольшим расширением, помимо того, что нам нужно для неподвижных генераторов, можно использовать закрепление для безопасной инкапсуляции гарантий навязчивых списков. Ralf написал описание этого расширения здесь .

По сути, гарантия закрепления такова:

Если у вас есть PinMut<T> , а T не реализует Unpin , T не будет перемещен, а резервная память T не будет будет признан недействительным до тех пор, пока деструктор не запустится для T .

Это не совсем то же самое, что «свобода утечки» - T все еще может протекать, например, застряв за циклом Rc, но даже если утечка происходит, это означает, что память никогда не станет недействительной (до тех пор, пока программа завершается), потому что деструктор никогда не запустится.

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

  1. Были обнаружены другие, даже более эргономичные API закрепления стека, такие как этот макрос , устраняющие обратную сторону.
  2. Потенциал роста довольно большой! Подобные навязчивые списки могут очень сильно повлиять на библиотеки, такие как josephine, которые интегрируют Rust с языками, собирающими мусор.
  3. Ральф никогда не был уверен, что некоторые из этих API с самого начала действительно работают (особенно те, которые основаны на «постоянном заимствовании»).

Реорганизация API

Тем не менее, я хочу внести одно последнее предлагаемое изменение в API, а именно переместить вещи. Изучив существующие API, мы с Ральфом поняли, что не было подходящего места для описания гарантий и инвариантов высокого уровня, относящихся к Drop и так далее. Поэтому предлагаю

  1. Создаем новый модуль std::pin . Документация модуля предоставит общий обзор закрепления, гарантии, которые он предоставляет, и инвариантов, которые должны соблюдать пользователи его небезопасных частей.
  2. Мы перемещаем PinMut и PinBox из std::mem и std::boxed в модуль pin . Мы оставляем Unpin в модуле std::marker с другими характеристиками маркера.

Нам также нужно будет написать более обширную документацию, прежде чем мы закончим стабилизацию этих типов.

Добавление Unpin реализаций

Ральф и я также обсудили добавление дополнительных реализаций Unpin в стандартную библиотеку. При реализации Unpin всегда есть компромисс: если Unpin реализуется безоговорочно в отношении типа поля, вы не можете закрепить проект в этом поле. По этой причине мы пока не очень агрессивно внедряли Unpin .

Однако мы считаем, что, как правило:

  1. Проекцию булавки через указатель никогда не следует рассматривать как безопасную (см. Вставку для примечаний ниже).
  2. Проекцию булавки из-за внутренней изменчивости никогда не следует рассматривать как безопасную.

В общем, типы, выполняющие одно из этих действий, делают что-то (например, перераспределяют резервное хранилище, как в Vec ), что делает проекцию булавки неприемлемой. По этой причине мы думаем, что было бы целесообразно добавить безусловную реализацию Unpin ко всем типам в std, которые содержат универсальный параметр либо за указателем, либо внутри UnsafeCell; это включает, например, все std::collections .

Я не исследовал слишком внимательно, но считаю, что для большинства из этих типов будет достаточно, если мы просто добавим эти импы:

impl<T: ?Sized> Unpin for *const T { }
impl<T: ?Sized> Unpin for *mut T { }
impl<T: ?Sized> Unpin for UnsafeCell<T> { }

Однако есть одна неприятность: технически мы считаем, что проецирование вывода через Box можно сделать технически безопасным: то есть переход от PinMut<Box<T>> к PinMut<T> может быть выполнен безопасно. Это потому, что Box полностью принадлежит указателю, который никогда не перераспределяется.

Возможно, мы хотим создать дыру, чтобы Unpin только условно реализовывалось для Box<T> когда T: Unpin . Однако мое личное мнение состоит в том, что можно думать о закреплении как о блоке памяти, который был закреплен на месте, и поэтому любое проецирование посредством косвенного обращения указателя должно быть небезопасным.

Мы можем отложить это решение, не блокируя стабилизацию, и со временем добавить эти имплементации.

Заключение

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

@rfcbot fcp слияние

Я предлагаю стабилизировать существующие API контактов после небольшой реорганизации. Вы можете прочитать более подробное описание в моем предыдущем посте . TL; DR, на мой взгляд, заключается в том, что текущий API:

  • Звук
  • Как хорошо, без смены языка
  • Прямая совместимость с изменениями языка, которые сделают его лучше

Мы проработали все основные вопросы, касающиеся точных гарантий и инвариантов закрепления за последние несколько месяцев, решения, которые мы принимаем в настоящее время (которые, я думаю, являются решениями, которые мы должны стабилизировать), задокументированы в более длинном посте.

Я предлагаю несколько изменений, прежде чем мы действительно стабилизируем ситуацию:

  1. Реорганизуйте PinMut и PinBox в новый модуль std::pin , который будет содержать документацию высокого уровня, касающуюся инвариантов и гарантий закрепления в целом.
  2. Добавьте безусловные реализации Unpin для типов в std, которые содержат общие параметры либо за косвенным указателем, либо внутри UnsafeCell; обоснование того, почему эти реализации подходят, содержится в более подробном изложении.

Член команды @withoutboats предложил объединить это. Следующий шаг - проверка остальными отмеченными командами:

  • [] @Kimundi
  • [] @SimonSapin
  • [] @alexcrichton
  • [] @dtolnay
  • [] @sfackler
  • [x] @withoutboats

В настоящее время не перечислено никаких проблем.

Как только большинство рецензентов одобрит (и никто не возражает), наступит последний период для комментариев. Если вы заметили серьезную проблему, которая не поднималась ни на одном этапе этого процесса, сообщите об этом!

См. Этот документ для получения информации о том, какие команды могут дать мне члены команды с тегами.

@rfcbot fcp отменить

Я помечаю lang также из-за основных инвариантных решений, которые нам пришлось принять, поэтому я отменяю и перезапускаю FCP.

Предложение @withoutboats отменено.

@rfcbot fcp merge См. предыдущее сообщение о слиянии и полное резюме, извините!

Член команды @withoutboats предложил объединить это. Следующий шаг - проверка остальными отмеченными командами:

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [] @joshtriplett
  • [] @nikomatsakis
  • [x] @nrc
  • [] @pnkfelix
  • [x] @scottmcm
  • [] @sfackler
  • [x] @withoutboats

Проблемы:

Как только большинство рецензентов одобрит (и никто не возражает), наступит последний период для комментариев. Если вы заметили серьезную проблему, которая не поднималась ни на одном этапе этого процесса, сообщите об этом!

См. Этот документ для получения информации о том, какие команды могут дать мне члены команды с тегами.

Я рад видеть некоторую уверенность в том, что это правильно. Однако трудно понять все, что изменилось с момента выхода оригинального RFC; можно ли обновить текст RFC, чтобы отразить проект, который предлагается для стабилизации? (В общем, это проблема, с которой сталкиваются многие RFC, поскольку они так сильно меняются в течение периода реализации, что попытки понять ее, прочитав RFC, бесполезны. Мы должны найти способ улучшить это.)

RFC не являются спецификацией. Чтение RFC не заменяет фактическую документацию.

@bstrie, значительное изменение от RFC ( Unpin безопасно) описано в моем итоговом посте. В долгосрочной перспективе люди должны понимать API закрепления, читая документацию в std::pin (блокировщик стабилизации), а не раскапывая исходный RFC.

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

@bstrie - это наша стандартная процедура - предлагать FCP перед документацией. я написал очень обширный сводный комментарий о состоянии игры, который должен предоставить достаточно информации для всех, кто не следил за проблемой отслеживания, а также более короткий комментарий для людей, которым не нужен такой подробный контекст

Если уточнить детали, FCP - это вопрос «хотим ли мы стабилизировать это?» Если ответ окажется отрицательным, значит, работа над документами потрачена зря. Завершение FCP не означает, что функция мгновенно становится стабильной; это означает, что было принято решение стабилизировать его, и теперь пора проделать необходимую для этого работу; это включает работу над компилятором и документацией.

2 августа 2018 г., в 12:56, лодки [email protected] написали:

@bstrie - это наша стандартная процедура - предлагать FCP перед документацией. Я написал очень обширный сводный комментарий о состоянии игры, который должен предоставить достаточно информации для всех, кто не следил за проблемой отслеживания

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

@withoutboats

Проекцию булавки через указатель никогда не следует рассматривать как безопасную (см. Вставку для примечаний ниже).
Проекцию булавки из-за внутренней изменчивости никогда не следует рассматривать как безопасную.

Не могли бы вы немного прояснить этот момент? Вы конкретно имеете в виду типы в стандартной библиотеке ? Подобно Box , существуют такие типы, как Mutex которые могут проецировать закрепление, поскольку они однозначно владеют своим содержимым (хотя они могут быть вне диапазона). По крайней мере, за пределами стандартной библиотеки, я бы предположил, что нам нужна возможность иметь примитивы параллелизма, которые проецируют PinRef<InPlaceMux<T>> на PinMut<T> при вызове .lock() , что кажется как в случае «проекции через внутреннюю изменчивость».

@cramertj Проектирование в Mutex кажется опасным; можно использовать безопасное преобразование в &Mutex а затем в Mutex::lock чтобы получить &mut а затем убрать все.

РЕДАКТИРОВАТЬ: О, вы имеете в виду всегда закрепленный Mutex . Мол, PinMutex . Да, это сработает. Внутренняя изменчивость - это нормально, если вы никогда не раздаете &mut . Но в настоящее время кажется маловероятным, что у нас будут такие структуры данных в libstd.

Но да, для полной точности оператор @withoutboats следует изменить так, чтобы он всегда булавит , т. &mut ».

@RalfJung Совершенно верно .

Но в настоящее время кажется маловероятным, что у нас будут такие структуры данных в libstd.

Правильно, я хотел убедиться, что заявленные требования, касающиеся отказа от проецирования с помощью указателей или внутренней изменчивости, касались именно libstd, а не общих правил.

@cramertj это определенно не жесткое и быстрое правило (например, не UB нарушать его), но я бы сказал, что это хорошее руководство для людей, которые не уверены

Я разочарован решением стабилизировать ситуацию. Меня не слишком беспокоит то, что произойдет в краткосрочной перспективе, поскольку без поддержки компилятора любой потенциальный дизайн будет выглядеть немного хакерским. Однако в долгосрочной перспективе, если Rust получит собственные неподвижные типы, они не будут чувствовать себя по-настоящему первоклассными, если их нельзя будет использовать с методами свойств, принимающими &self и &mut self . Как следствие, закрепленность должна быть свойством референтного типа, а не ссылочного типа. Конечно, это все еще может произойти в будущем проекте для естественных недвижимых типов. Но это приведет к тому, что Pin<T> станет избыточным - не только, скажем, устаревшим псевдонимом для некоторого родного &pin T , но и вовсе ненужным. В свою очередь, такие трейты, как Future (и, возможно, многие другие), которые принимают Pin<Self> , необходимо будет обновить или исключить из рекомендаций, что потребует сложного переходного периода.

Беспорядочно, не невозможно. Но, рискуя оказаться излишне пессимистичным, я обеспокоен тем, что желание избежать такого перехода приведет к смещению будущих решений в пользу принятия вместо этого &pin в качестве основы для естественных недвижимых типов, что, на мой взгляд, оставит они постоянно второсортные. И я все еще думаю, что среди краткосрочных проектов подход &Pinned<T> был бы осуществим, избегая этих проблем прямой совместимости, а также имел бы другие преимущества (например, полностью обойдя проблему внутренней изменчивости).

Ну что ж. Как бы то ни было, я все еще с нетерпением жду возможности использовать Pin , но я бы предпочел, чтобы он остался на crates.io, а не входил в стандартную библиотеку.

Я согласен, что еще рано это стабилизировать. В настоящее время это несколько неудобно в некоторых ситуациях, например, при доступе к полям типа !Unpin внутри метода, вызываемого PinMut<Self> . Я думаю, что изменения

@Thomasdezeeuw Какие изменения, по вашему мнению, могут улучшить ситуацию. Предложенные мной изменения, похоже, не связаны с этим неудобством.

Невозможно написать общее решение, то есть добавить метод в std, но этот вариант макроса из Futures безопасен:

macro_rules! get_mut {
    ($f:tt: $t:ty) => (
        fn $f<'a>(
            self: &'a mut std::mem::PinMut<Self>
        ) -> &'a mut $t
            where $t: Unpin
        {
            unsafe {
                 &mut std::mem::PinMut::get_mut_unchecked(self.reborrow()).$f
            }
        }
    )
}

struct Foo {
    bar: Bar,
}

impl Foo {
     get_mut!(bar: Bar);
}

@withoutboats Это безопасно, потому что предоставляет доступ только к Unpin данным, верно?

@RalfJung точно! строка предложения where делает его безопасным

@withoutboats - хороший макрос, но работает только для типов Unpin .

Но разве нельзя было бы добавить метод / макрос, который принимал бы PinMut<Self> и возвращал бы PinMut<Self.field> , желательно в безопасном коде. Я могу ошибаться, но я думаю, что если вызывающий гарантирует, что Self закреплен, то и Self.field - это тоже самое, верно? Это также должно работать для типов, которые не реализуют Unpin .

@Thomasdezeeuw Эта проблема обсуждается в моем резюме в разделе «Пин-проекция»; небезопасно проецировать на поля !Unpin если вы также не гарантируете, что Self не выполняет некоторых других действий, для которых мы не можем создать проверку. Мы можем только автоматически гарантировать возможность проецирования в поля, которые реализуют Unpin (потому что это всегда безопасно, и мы можем проверить это с помощью предложения where ).

@withoutboats Я прочитал резюме, но, возможно, я неправильно понял. Но если тип T равен !Unpin тогда PinMut<T> не будет реализовывать DerefMut for T , поэтому перемещение значения было бы невозможно без небезопасного кода. У вас все еще есть проблема с трейтом Drop , но это больше, чем просто получение доступа к полю типа. Поэтому я не понимаю, что делает преобразование из PinMut<T> в PinMut<T.field> небезопасным, даже если T.field равно !Unpin .

@Thomasdezeeuw В futures-rs бывают ситуации, когда у нас есть тип внутри PinMut , но одно из его полей не считается закрепленным

Обратной стороной макроса self . Доступ к полю структуры не имеет этого ограничения.

Я считаю, что решение стабилизировать API закрепления - правильное решение. Однако я бы предложил сначала реализовать предложенный модуль core::pin .

У вас все еще есть проблема с трейтом Drop, но это больше, чем просто получение доступа к полю типа.

Нет, это не так. Drop на самом деле нормально, если вы не выполняете сопоставление полей . Вот почему отображение полей небезопасно.

У вас все еще есть проблема с трейтом Drop, но это больше, чем просто получение доступа к полю типа. Поэтому я не понимаю, что делает отображение в PinMutв PinMutнебезопасно, даже если T.field есть!

Чтобы было ясно, мы также не можем автоматически сгенерировать доказательство того, что T: !Unpin . Но даже если это не так, вот пример того, как можно использовать Drop impl:

struct Foo {
    field: UnpinFuture,
}

impl Foo {
     fn field(self: PinMut<Self>) -> PinMut<UnpinFuture> { ... }

     fn poll_field(self: PinMut<Self>, ctx: &mut Context) {
         self.field().poll(ctx);
     }
}

impl Drop {
    fn drop(&mut self) {
        // ...
        let moved_field = mem::replace(&mut self.field, UnpinFuture::new());

        // polling after move! violated the guarantee!
        PinBox::new(moved_field).as_pin().poll();
    }
}

Как говорит @RalfJung , это как раз проблема с Drop - если вы не делаете никаких выводов на поля !Unpin , все, что вы делаете в Drop , нормально.

Обратной стороной макроса

Вы можете написать метод, предоставляющий доступ к нескольким полям, но вам придется делать это для каждой интересующей вас комбинации.

Из любопытства, есть ли другие конкретные варианты использования, помимо фьючерсов, или, по крайней мере, что-то еще, мотивирующее желание стабилизировать их сейчас (а не позже, с большим опытом)?

Кажется, здесь не будет дальнейшего обсуждения вопросов, поднятых @comex. Они вызвали у меня большое любопытство, потому что я не припоминаю идеи сделать pin свойством типа вместо ссылки. Об этом уже где-то говорили? За всем следить «со стороны» непросто, стараюсь ;-)

В свою очередь, такие черты, как Future (и, возможно, многие другие), которые принимают Pinнеобходимо будет обновить или исключить из поддержки, что потребует нелегкого переходного периода.

Хммм. Может ли магия, связанная с редакцией, позволить нам незаметно завернуть и развернуть эти Pin и сделать изменения полностью обратно совместимыми? Вроде грубо, но не слишком, и это позволит сохранить язык и API в чистоте во время такого перехода.

Возможно, для ответа на этот вопрос потребуется более конкретная идея (я помню, что об этом уже говорилось ранее, но я не помню, где именно).

@yasammez Я смог подхватить обсуждение ценностно-ориентированного закрепления здесь

Просто чтобы подтвердить, что на пути к стабилизации нет плана безопасных прогнозов месторождения? Мне кажется, это приведет к появлению большого количества небезопасного кода во всех библиотеках Futures. По моему опыту, ручные имплементы будущего - не редкость, и я знаю, что все мои требуют опроса полей.

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

@tikue в ближайшем будущем вы можете:

  1. используйте макрос, такой как макрос unsafe_pinned , разработанный библиотекой Futures, ограничивая себя одним небезопасным для каждого внутреннего опроса, который вы должны проверять на соответствие ограничениям, описанным в моем длинном резюме (вы также не можете использовать макрос и просто напишите аксессор вручную, это также только один unsafe ).
  2. требовать, чтобы в ваших полях реализовывалось Unpin , что делает такие прогнозы тривиально безопасными и не требует небезопасного кода за счет выделения кучи любых фьючерсов !Unpin .

@yasammez подробно обсуждался в этой теме: https://internals.rust-lang.org/t/can-pin-own-its-referent-instead-of-borrowing-mutably/7238/20

@withoutboats Я понимаю варианты, но нахожу их Unpin ограничит совместимость с целым рядом фьючерсов, таких как все, что заимствует сам, например, любой async fn, использующий каналы. А небезопасное загрязнение - реальная проблема.

Фактически, я пробовал перенести библиотеку на Futures 0.3 и обнаружил, что по этим причинам это сложно.

@tikue Это совместимо с предоставлением вам чека в автоматическом режиме. Мы еще не полностью разработали и не реализовали это, но ничто здесь не препятствует этому.

Однако я не совсем понимаю эту проблему:

А небезопасное загрязнение - реальная проблема.

Я чувствую, что «проблема небезопасности» слишком часто остается на этом уровне - просто общий запрет на unsafe .

Для большинства вложенных фьючерсов должно быть очень легко определить, можно ли безопасно использовать макрос unsafe_pinned! . Контрольный список обычно выглядит так:

  1. Я не реализовал вручную Unpin для своего будущего, вместо этого я просто полагаюсь на auto trait impl.
  2. Я не реализовывал вручную Drop для своего будущего, потому что у меня нет специального деструктора.
  3. Поле, на которое я хочу выполнить проецирование, является частным.

В таком случае: все хорошо! unsafe_pinned безопасен в использовании.

Если вы сделали вручную реализовать Unpin или Drop , то есть на самом деле думать об этом, но даже в этом случае его не обязательно такая сложная проблема:

  1. Если бы я действительно реализовал Unpin , это ограничивалось бы будущим, которое я абстрагирую, чем Unpin .
  2. Если бы я реализовал Drop , я бы никогда не сдвинул поле future во время своего деструктора.

@tikue для справки, я суть базового предложения для решения на основе атрибутов . Для этого требуется поддержка компилятора, но не значительные расширения языка - только одна новая черта и один новый встроенный атрибут.

Существует также более «навязчивое» решение, в котором мы добавляем правильные ссылочные типы &pin , которые будут иметь такую ​​же форму с точки зрения ограничений, но окажут большее влияние на язык в целом.

А как насчет PinMut::replace ?

impl<'a, T> PinMut<'a, T> {
  pub fn replace(&mut self, x: T) { unsafe {
    ptr::drop_in_place(self.inner as *mut T);
    ptr::write(self.inner as *mut T, x);
  } }
}

Даже если мы не хотим добавлять такой метод, у нас должен быть ответ на вопрос, безопасно ли это - для PinMut , а также для PinBox которые могут иметь нечто подобное.

Я думаю, что это определенно безопасно для PinBox , потому что я позабочусь о том, чтобы вызвать деструктор «на месте», и тогда это просто повторное использование памяти. Однако меня немного беспокоит PinMut поводу преждевременного отбрасывания значения, т. Е. До истечения срока его жизни. Мне не кажется очевидным, что это всегда будет нормально, но я также не могу привести контрпример. @cramertj @withoutboats есть мысли?

(Я бы добавил это в @rfcbot, если бы мог ...)

@rfcbot проблема заменить

@RalfJung Я могу ошибаться здесь, но разве этот код не выпадет T ?

@RalfJung У нас уже есть PinMut::set .

@cramertj D'oh. Да ладно, я мог бы проверить это более тщательно.

Извините за шум, моя проблема решена.

@rfcbot разрешить заменить

Это настоящий код, который я пишу прямо сейчас для совместимости фьючерсов 0.1 => 0.3 с tokio_timer::Deadline . Необходимо создать PinMut<Future01CompatExt<Delay>> , где Delay - это поле Deadline .

        let mut compat;
        let mut delay = unsafe {
            let me = PinMut::get_mut_unchecked(self);
            compat = Future01CompatExt::compat(&mut me.delay);
            PinMut::new_unchecked(&mut compat)
        };

Помимо нетривиального использования unsafe, мне потребовалось еще около 5 итераций этого кода, чтобы найти версию, которая скомпилирована. Писать и думать о том, что здесь происходит, довольно неэргономично.

Я думаю, что между общим запретом на небезопасное и этим кодексом есть много места. Например, map_unchecked слишком ограничивает, чтобы помочь с этим кодом, потому что он требует возврата ссылки, тогда как этот код требует возврата Future01CompatExt<&mut Delay> .

Изменить: еще один источник неэргономичности - вы не можете взаимно заимствовать у спичечных охранников, поэтому PinMutне может работать с таким кодом:

ржавчина
сопоставить self.poll_listener (cx)? {
// раньше было если self.open_connections> 0
Poll :: Ready (Нет), если self.open_connections ()> 0 => {
^^^^ заимствовано взаимно в защите паттернов
return Poll :: Pending;
}
Опрос :: Готово (Нет) => {
return Poll :: Ready (Нет);
}
}
`` ''

Ваш комментарий о map_unchecked уместен, возможно, есть более общая подпись, которая также может позволить возвращать временные значения вокруг указателей.

Существуют ли инструкции, когда безопасно получить ссылку &mut из поля PinMut<Type> ? Я думаю, если Type никогда не предполагает, что поле закреплено, это нормально? Это должно быть личное?

Да, либо поле реализует Unpin либо вы никогда не создаете PinMut поля.

@rfcbot дисперсия

Я пишу приличное количество кода на основе PinMut и одна вещь, которая произошла часто, это то, что у меня есть операция, которая не полагается на закрепление, но берет себя на PInMut а не &mut как способ сказать: «Я обещаю, что не уйду из этой памяти». Однако это заставляет всех пользователей иметь закрепленное значение, в чем нет необходимости. Я долго думал над этим и не смог придумать для этого хороший API, но было бы замечательно, если бы был способ отделить «мне нужно, чтобы мой аргумент был закреплен» от «я могу работать с закрепленным аргумент ".

Я думаю, что «дисперсия» - неправильный термин; то, что вы хотите, - это связанные конструкторы типов для абстрагирования по различным ссылочным типам: D

@RalfJung Да, это правда. Тем не менее, я бы согласился на тип, который может быть получен от PinMut или от & mut, но может использоваться только как PinMut, хотя я знаю, что это не особенно хорошо масштабируется. Я думаю, что это более достижимо, чем универсальный random_self_types: smile: Возможно, лучше просто закончить словами «мы вполне уверены, что мы готовы к будущим функциям таких сигнатур»:

impl MyType {
    fn foo(self: impl MutableRef<Self>, ...) { ... }
}

@rfcbot проблема api-refactor

Немного вдохновляющей структуры, и вчера вечером я понял, как мы можем реорганизовать этот API, чтобы был только один тип Pin , который обертывает указатель, вместо того, чтобы создавать закрепленную версию каждого отдельного указателя. Это никоим образом не является фундаментальным изменением API, но кажется, что лучше вытащить компонент «фиксирует память» в составную часть.

Когда я работал над сетевым стеком для nt-rs , мне сказали, что закрепленные ссылки помогут с «танцем владения», который я сейчас решаю с помощью fold как показано здесь (tx.send перемещает tx в Send<<type of tx>> future, что затрудняет перебор входящих данных для отправки пакетов.). Каким образом закрепление поможет в подобных делах?

@Redrield send() принимает &mut self во фьючерсе 0.3.

@withoutboats Я очень хочу попробовать этот новый API в futures-rs!

Поскольку новый API использует разные идентификаторы, я думаю, что оба API будут доступны одновременно. Я думаю, что было бы лучше иметь короткий переходный период, в течение которого доступны оба API, что позволяет нам экспериментировать, готовить PR и переносить Futures API в libcore в новый стиль.

@MajorBreakfast кажется, что мы могли бы потенциально использовать псевдоним type PinMut<T> = Pin<&mut T>; и предложить ряд тех же методов, которые сократят явную величину и непосредственность поломки.

@withoutboats

Итак, с новым API:

  1. Pin<&T> и Pin<&mut T> имеют инвариант "стандартного закрепления", где стоящее за ними значение:
    1.a. больше не является допустимым &T / &mut T , если только Unpin
    1.b. не будет использоваться как Pin из другого адреса памяти.
    1.c. будет отброшен до того, как память станет недействительной.
  2. Pin<Smaht<T>> не имеет никаких «специальных» гарантий, за исключением того, что он возвращает действительные Pin<&mut T> и Pin<&T> запросу (что является стандартной гарантией безопасности, учитывая, что API безопасны. ).
    2.a. Нужна ли нам гарантия DerefPure , где Pin<Smaht<T>> требуется для возврата того же значения, если оно не изменено? Кто-нибудь этого хочет?

Поправка об инварианте.

Инвариант для Pin<Smaht<T>> :

  1. Вызов Deref::deref(&self.inner) даст действительный Pin<&T::Target> (обратите внимание, что &self.inner не обязательно должен быть безопасным &Smaht<T> ).
  2. Если Smaht<T>: DerefMut , вызов DerefMut::deref_mut(&mut self.inner) даст действительный Pin<&mut T::Target> (обратите внимание, что &mut self.inner не обязательно должен быть безопасным &mut Smaht<T> ).
  3. Вызов деструктора self.inner для его уничтожения - это нормально, если он безопасен для уничтожения.
  4. self.inner не обязательно должен быть безопасным Smaht<T> - он не должен поддерживать другие функции.

В сообщении Reddit был опубликован отзыв о предложении @withoutboats :

  • (несколько) Own - странное имя, см. ниже возможные способы решения этой проблемы.

  • ryani спрашивает, почему реализации, ограничивающие Deref<Target = T> имеют этот дополнительный общий T ? Разве вы не можете просто использовать P::Target ?

  • jnicklas спрашивает, какова цель трейта Own , а как насчет добавления pinned собственных методов по соглашению? Это означает, что пользователям API не нужно импортировать трейт, однако вы теряете возможность быть универсальным по сравнению с закрепляемыми конструкторами. Это большое дело? Мотивация этой черты кажется недостаточно мотивированной: _ [они] в конце концов имеют одинаковую форму ».

  • RustMeUp (я) спрашивает в трейте Own , какова цель метода own ? Почему черта не может оставить pinned для реализации типами, которые хотят принять участие? Это позволяет признаку быть безопасным и позволяет ему называться Pinned что менее неудобно, чем Own . Причина использования собственного метода кажется недостаточно мотивированной.

Добавлю еще вопрос:

Почему ни Pin<P> , ни Pin<P>::new_unchecked ограничены P: Deref ?

#[derive(Copy, Clone)]
pub struct Pin<P> {
    pointer: P,
}

impl<P: Deref> Pin<P> { // only change
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

или же

#[derive(Copy, Clone)]
pub struct Pin<P: Deref> { // changed
    pointer: P,
}

impl<P: Deref> Pin<P> { // changed
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

оба предотвратят Pin<P> где P: !Deref экземпляры, которые бесполезны, поскольку с ним ничего нельзя сделать, если не будут добавлены дополнительные методы. Я думаю...

Вот суть с моей и ryani обратной связью.

ryani спрашивает, почему реализации, которые ограничивают Дерефаесть этот дополнительный общий T? Разве вы не можете просто использовать P :: Target?

Между этими двумя имплементациями нет никакой разницы, кроме того, как они написаны.

Почему ни ПИН

сам ни пин

:: new_unchecked ограничено P: Deref?

У меня нет мнения; исторически библиотека std не привязывалась к структурам, но я не уверен, является ли эта политика хорошо мотивированной или исторической случайностью.


Планируется реализовать это после приземления # 53227, но не будет реализовывать черту Own потому что это вызывает споры. На данный момент только встроенный конструктор pinned для Box , Rc и Arc .

Следуя тому, что написал @ arielb1 , я рассматриваю здесь Pin как действительное действие на «конструкторы типа указателя» - такие вещи, как Box или &'a mut которые имеют «kind» * -> * . Конечно, у нас на самом деле нет синтаксиса для этого, но это способ, которым я могу понять это с точки зрения инвариантов. У нас все еще есть 4 (безопасность (инварианты для каждого типа, как в моем сообщении в блоге : Owned, Shared, Pinned-Owned, Pinned-Shared.

Я думаю, что для правильной формализации требуется это представление, потому что идея состоит в том, что Pin<Ptr><T> принимает инварианты T , преобразует их (везде используя закрепленные инварианты), а затем применяет Ptr конструктор к этому результирующему типу. (Для этого нужно изменить способ определения Owned , но я все равно планировал это в долгосрочной перспективе.) С точки зрения формализма, действительно лучше было бы написать Ptr<Pin<T>> , но мы попробовали это не очень хорошо работает с Rust.

Таким образом, конструктор типа указателя дает обещание, когда "выбирает" Pin заключается в том, что применение Deref / DerefMut будет безопасным: это когда на самом деле ввод Ptr примененный к варианту типа «Принадлежащий закрепленным / общий доступ», выходные данные будут соответствовать варианту типа «Принадлежащий закрепленному / общему доступу».

На самом деле это единственные два "небезопасных метода" (в том смысле, что они должны быть осторожны с инвариантами):

impl<P, T> Pin<P> where
    P: Deref<Target = T>,
{
    pub fn as_ref(this: &Pin<P>) -> Pin<&T> {
        Pin { pointer: &*this.pointer }
    }
}

impl<P, T> Pin<P> where
    P: DerefMut<Target = T>,
{
    pub fn as_mut(this: &mut Pin<P>) -> Pin<&mut T> {
        Pin { pointer: &mut *this.pointer }
}

Все остальное, что безопасно для использования, может быть реализовано поверх этого с помощью безопасного кода, используя то, что Pin<&T> -> &T является безопасным преобразованием, и для типов Unpin то же самое относится к Pin<&mut T> -> &mut T .

Я бы предпочел, чтобы мы могли изменить реализации Deref и DerefMut для Pin на что-то вроде

impl<'a, T: Unpin> Pin<&'a mut T> {
    pub fn unpin_mut(this: Pin<&'a mut T>) -> &'a mut T {
        this.pointer
    }
}

impl<'a, T> Pin<&'a T> {
    // You cannot move out of a shared reference, so "unpinning" this
    // is always safe.
    pub fn unpin_shr(this: Pin<&'a T>) -> &'a T {
        this.pointer
    }
}

// The rest is now safe code that could be written by users as well
impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        Pin::unpin_shr(Pin::as_ref(self))
    }
}

impl<P, T> DerefMut for Pin<P> where
    P: DerefMut<Target = T>,
    T: Unpin,
{
    fn deref_mut(&mut self) -> &mut T {
        Pin::unpin_mut(Pin::as_mut(self))
    }
}

Это поможет понять, что эти двое не делают ничего нового , они просто составляют as_ref / as_mut с безопасным преобразованием из Pin<&[mut] T> в &[mut] T - - что явно оставляет as_ref / as_mut как единственное, о чем должны беспокоиться другие конструкторы типов указателей.


В текущем PinMut есть метод borrow использующий &mut self для облегчения повторного заимствования (поскольку для этого требуется self , мы получаем автоматическое заимствование). Этот метод делает то же самое, что и Pin::as_mut . Полагаю, нам здесь тоже нужно что-то подобное?

Я только что заметил, что у срезов есть get_unchecked_mut , а у булавки get_mut_unchecked . Кажется, мы должны придерживаться чего-то последовательного?

Может ли кто-нибудь добавить это к проблемам rfcbot?

@rfcbot проблема get_mut_unchecked_mut_mut

Я только что понял, что мы забыли об одной ситуации, когда rustc будет копировать все вокруг - хотя это, вероятно, пока не имеет большого значения. Я говорю о упакованных структурах. Если в упакованной структуре есть поле, которое нужно удалить, rustc выдаст код для копирования данных этого поля в выровненное место, а затем вызовет для этого drop . Это необходимо для того, чтобы убедиться, что &mut переданный в drop , действительно выровнен.

Для нас это означает, что структура repr(packed) не должна быть «структурной» или «рекурсивной». закрепление - его поля не могут считаться закрепленными, даже если сама структура. В частности, небезопасно использовать макрос pin-accessor в такой структуре. Это следует добавить в его документацию.

Это похоже на гигантское ножное ружье, которое должно быть наклеено на все прикрепляемые документы.

@alercah Я не думаю, что это больше похоже на ножное ружье, чем существующая возможность использовать Unpin или Drop - конечно, оба гораздо чаще встречаются вместе с закрепленными значениями, чем #[repr(packed)] .

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

Хм, будет ли это также означать, что все упакованные структуры могут быть Unpin независимо от типов полей, поскольку закрепление не рекурсивно?

@alercah Я не супер знаком с тем, как реализуются упакованные типы, но я считаю его смела булавку упакованного типа, просто не штифт через упакованный тип. Таким образом, единственный случай, когда вы не контролируете упакованный тип, - это если вы проецируете публичное поле другого типа, что может быть столь же небезопасным из-за Drop или чего-то еще. В общем, кажется нецелесообразным прикреплять проект к чужим полям, если только они не предоставят проекцию булавки, указывающую, что это безопасно.

Хм, будет ли это также означать, что все упакованные структуры могут быть откреплены независимо от типов полей, поскольку закрепление не рекурсивно?

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

Мне пришлось изучить Pin API из-за моей работы с Futures, и у меня есть вопрос.

  • Box безоговорочно реализует Unpin .

  • Box безоговорочно реализует DerefMut .

  • Метод Pin::get_mut всегда работает с &mut Box<T> (потому что Box безусловно реализует Unpin ).

Все вместе это позволяет перемещать закрепленное значение с полностью безопасным кодом .

Это предназначено? В чем причина того, почему это безопасно?

Похоже, у вас есть Pin<&mut Box<...>> , который закрепляет Box , а не содержимое Box . Всегда безопасно перемещать Box , потому что Box никогда не хранит ссылки на свое местоположение в стеке.

Скорее всего, вы захотите Pin<Box<...>> . Ссылка на площадку .

РЕДАКТИРОВАТЬ: больше неточно

@Pauan То, что Box закреплено, не означает, что объект _внутренний_ закреплен. Любой код, зависящий от этого, был бы неверным.

То, что вы ищете, вероятно, это PinBox , что запрещает указанное вами поведение и позволяет получить PinMut<Foo> .

@tikue Все еще возможно выйти из Pin<Box<...>> , чего, я думаю, они и пытались избежать.

@tmandry Поправьте меня, если я ошибаюсь, но Pin<Box<...>> закрепляет предмет внутри Box , а не сам ящик. В исходном примере @Pauan у них был Pin<&mut Box<...>> , который закреплял только Box . Смотрите мою ссылку на игровую площадку, показывающую, как Pin<Box<...>> предотвращает получение изменяемой ссылки на вещь в коробке.

Обратите внимание, что PinBox был недавно удален, а Pin<Box<T>> теперь имеет ту же семантику, что и PinBox .

@tmandry PinBox<T> был удален и заменен на Pin<Box<T>> в Nightly (ссылка на документ, которую вы указали, предназначена для Stable). Вот правильная ночная ссылка.

О, правила, должно быть, изменились с тех пор, как я использовал их в последний раз. Извините за путаницу.

@tmandry Да, изменения произошли совсем недавно. Поскольку все еще в движении, трудно угнаться за всеми изменениями.

Комментарий @tikue правильный. Вы должны помнить, что булавки фиксируют только один уровень косвенного обращения вниз.

@tikue @tmandry @withoutboats Спасибо за ответы! Это было очень полезно.

Итак, каковы сейчас состояния этих двух проблем? ( api-refactor & get_mut_unchecked_mut_mut ) Как человек, с нетерпением ожидающий появления функции async / await, мне интересно, на какую версию rustc будут нацелены API Pin ? Есть оценка?

@ crlf0710 см. предложение по

@withoutboats Кажется, готово? Мы закроем?

Хорошо, прошу прощения, если это не место для публикации этого, но я думал о проблеме Drop + !Unpin и пришел к следующей идее:

  1. В идеале, если бы Drop::drop было fn(self: Pin<&mut Self>) , проблем не было бы. Назовем такой Drop PinDrop . Мы не можем просто заменить Drop на PinDrop из-за проблем с ретро-совместимостью.
  1. поскольку единственная проблема с Drop::drop(&mut self) - это случай Drop + !Unpin , мы могли бы получить имплицит по умолчанию PinDrop для Drop + Unpin (с тех пор Pin<&mut T> : DerefMut<Target = T> ) и сделайте PinDrop признаком, автоматически используемым rustc (благодаря Pin::new_unchecked(&mut self) , так как drop - единственный случай закрепления стека, когда мы думаем об этом).

Вот схематичный PoC идеи: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9aae40afe732babeafef9dab3d7525a8

В любом случае, imho это должно оставаться в beta и еще не стать стабильным, даже если это должно быть исключением. Если есть время, когда поведение Drop может зависеть от Unpin без нарушения совместимости, то это время сейчас.

@danielhenrymantilla Я не понимаю, как это решает проблему совместимости с существующими типичными имплементами drop, такими как impl<T> Drop for Vec<T> .

Вы правы, требуется другое:
неявная привязка T: Unpin ко всем дженерикам с отказом от использования ?Unpin же, как Sized

Это должно заставить его стать

impl<T> Drop for Vec<T>
where
  T : Unpin, // implicit, which implies that Vec<T> : Unpin which "upgrades" `Drop` into `PinDrop`

неявная привязка T: Unpin ко всем дженерикам с отказом от использования ?Unpin же, как Sized

Это имеет огромное влияние на общий дизайн API и широко обсуждалось в рамках предложений ?Move . Например, это может означать, что многие, многие существующие библиотеки потребуют обновления для работы с закреплением. Был сделан вывод, что использование решения только для библиотеки, такого как то, что мы получили сейчас, лучше, потому что оно не требует ничего из этого.

Да, в краткосрочной перспективе огромные затраты, так как все существующие библиотеки необходимо будет обновить, чтобы они были совместимы с !Unpin , но в конечном итоге мы получим «более безопасный» Drop . Сначала это не казалось таким уж плохим, так как мы по крайней мере ничего не ломаем.

Но это справедливое беспокойство (я не знал, что он поднимался ранее; спасибо, что указали на него, @RalfJung ), и я предполагаю, что краткосрочные практические недостатки drop(Pin<&mut Self>) .

Обсуждалась ли реализация Hash для хеширования типов Pin на адресах?

Pin вероятно, должен иметь реализацию Hash которая просто делегирует содержащийся указатель, нет приоритета для хеширования на основе адресов (и я не вижу причин, по которым закрепление значения должно изменить способ он хешируется).

Была ли эта страница полезной?
0 / 5 - 0 рейтинги