Rust: Pin API的跟踪问题(RFC 2349)

创建于 2018-03-18  ·  211评论  ·  资料来源: rust-lang/rust

rust-lang / rfcs#2349的跟踪问题

阻止稳定:

  • [x]实施(PR#49058)
  • []文档

未解决的问题:

  • []我们是否应该为泄漏!Unpin数据提供更有力的保证?

编辑:摘要评论: https :

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

最有用的评论

@rfcbot关注api重构

有点启发性的结构,昨晚我弄清楚了如何重构此API,以便只有一个Pin类型可以包装一个指针,而不必创建每个指针的固定版本。 这绝不是对API进行根本性的重塑,但是将“固定内存”组件拉成可组合的部分感觉更好。

所有211条评论

我现在注意到堆栈固定不属于RFC, @ withoutboats您打算为此发布一个包装箱,还是应该将示例代码复制到需要它的包装箱中?

@ Nemo157您应该复制它并报告您的经验!

关于泄漏Unpin数据的未解决问题与此相关。 如果我们说您不能覆盖Unpin中的Pin Unpin数据(除非析构函数按照@cramertj的要求运行),则该

我要指出的一件事是,在await!宏中,堆栈固定不足以完成Future::poll类的事情,因为它不允许我们循环轮询。 如果您遇到这些问题,以及解决问题的方式/方法,我将很感兴趣。

我当前的用法非常琐碎,一个单线程执行程序运行一个StableFuture而没有产生支持。 切换到@cramertj建议的API可以很好地解决此问题。 我想知道如何扩展它以允许生成多个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之类的东西还不够! 宏,因为它不允许我们循环轮询。

您能详细说明一下吗?

@RalfJung您需要Pin来支持作为Pin借入,目前尚不支持。

@cramertj听起来像是对Pin API的限制,而不是堆栈固定API的限制?

@RalfJung是的,这是正确的。 但是, PinBox可以重新借入Pin ,而堆栈固定类型不能借用(一次借用,因为Pin在整个借用类型的生存期内创建借用)。

给定Pin ,我可以将其作为&mut Pin借用,然后使用Pin::borrow -这是重新借入的一种形式。 我认为这不是您在谈论的那种boro骂?

@RalfJung不-像Future::poll被计划采用self: Pin<Self> ,而不是self: &mut Pin<Self> (这不是有效的self类型,因为它是' t Deref<item = Self> -这是Deref<Item = Pin<Self>> )。

可能是这样,我们实际上可以使它与Pin::borrow 。 我不确定。

@cramertj我不建议在x: &mut Pin<Self>上调用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 。 例如VecVecDequeBoxCellRefCellMutexRwLockRcArc 。 我希望大多数箱子都根本不会考虑固定,因此,如果它们的所有字段均为Unpin ,则它们的类型只能是Unpin Unpin 。 这是一个不错的选择,但它会导致不必要的弱接口。

如果我们确保为所有libstd指针类型(甚至包括原始指针)和UnsafeCell实现Unpin ,这是否可以解决? 那是我们要做的事情吗?

如果我们确保为所有libstd指针类型(甚至包括原始指针)和UnsafeCell实施Unpin,这是否可以解决? 那是我们要做的事情吗?

是的,对我来说似乎就像Send

我刚想到一个新问题: PinPinBox Send什么时候? 现在,只要TSend ,自动特征机制就使它们成为Send Send 。 没有先验的理由可以这样做。 就像共享typestate中的类型具有自己的可发送性标记特质(称为Sync )一样,我们可以制作一个标记特质来说明何时Pin<T>Send ,例如PinSend 。 原则上可以写Send而不是PinSend ,反之亦然。

&mut T发送时, @ 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,
        }
    }
}

(可以解决使用transmute强制使用生命周期的问题,但这使我感到非常讨厌)。

我想添加一个未解决的问题:是否应该添加回共享的固定引用类型? 我认为答案是肯定的。 有关更多详细信息,请参见带有讨论的帖子

我刚刚读到Futures 0.2并没有我想象的那么最终,因此也许毕竟仍然有可能将Pin重命名回PinMut并添加共享版本。

@RalfJung我会更彻底地阅读您的博客文章,以了解您建议的更改。

我认为您已经找到了具有不变的Pin变体的潜在引人注目的用例,但我不理解您对Deref&Pin<T> <=> &&T 。 即使Pin<T>可以安全地转换为&T ,也不能使它们等效,因为&T无法转换为Pin<T> 。 我看不出有使安全转换不安全的理由(通过消除安全的Deref impl)。

当前Pinmap方法具有签名

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::replacemem::swap移出其字段,这违反了先前对map “正确”使用所做出的承诺。

换句话说:对Pin::map使用“正确的引脚投影”调用(该调用看起来像unsafe { Pin::map(&mut self, |x| &mut x.p) } ),而没有其他调用unsafe ,则可以产生不正确/未定义行为。 这是一个演示此操作的游乐场链接。

这并不意味着当前的API有任何问题。 它表明Pin::map应该已经标记为unsafe了。 我也不认为人们在实施期货之类的交易时会有意外跳闸的危险-您真的必须竭尽全力以Drop

但是我确实认为map的文档注释可能要提及这一点,并且我也认为它会使我在RFC和周围讨论中看到的扩展名无效:

  • 应该有一个宏/导出,做“销突”,并使它看起来安全。 投影确实确实在围绕它的其他代码上施加了契约,编译器无法完全执行契约。 因此,它应该在完成后就要求使用unsafe关键字。
  • RFC提到,如果将Pin转换为&'a pin T语言功能,则“通过字段进行投影很简单”。 我相信我已经表明,即使仅限于投影专用字段,这仍然需要unsafe

@withoutboats

即使针可以安全地强制转换为&T,但这不会使它们等效,因为&T无法强制转换为Pin

确实,这不是充分条件。 它们在我的模型中是相等的,因为在我之前的文章中,我们做了以下定义:

定义5: PinBox<T>.shr 。 如果ptr是指向另一个指针inner_ptr的只读指针,例如T.shr('a, inner_ptr)指针ptr和寿命'a满足PinBox<T>的共享类型状态

(并且,隐含地, Pin<'a, T>.shr的相应定义。)
注意PinBox<T>.shr取决于T.shr而不是其他。 这使得PinBox<T>.shrBox<T>.shr不变,这意味着&Box<T> = &PinBox<T> 。 类似的推理表明&&T = &Pin<T>

因此,这不是RFC中记录的API或合同的结果。 这是该模型仅具有三种类型状态的结果:拥有,共享,固定。 如果要反对&&T = &Pin<T> ,则必须为引入第四个typestate(“共享固定”)而争论。

@MicahChalmer

在有关Pin的许多讨论中,似乎有一个假设认为应将Pin“投射”到私有字段上是安全的。 我认为那不是真的。

这是非常好的一点! 只是要清楚一点,公开Shenanigansp字段没有问题,在吗? 那时,任何客户端都可以编写do_something_that_needs_pinning ,并且RFC的目的是确保其安全性。 (我不知道为什么RFC专门提到私有字段,我的解释总是认为它适用于所有字段。)

有趣的是,这与我的模型中dropdrop(ptr)的前提是T.pin(ptr) 。 通过这种解释,示例中实际出错的代码将是drop实现! (现在,我想知道为什么在撰写本文时我还没有注意到这一点……)我想我们确实希望最终允许对字段进行安全的投影,我们真的不应该为drop提供&mut如果类型确实固定)。 显然是假的。

有什么方法可以使(a)如果类型为!Unpin使得impl Drop不安全,或者(b)如果T: !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作为共享的固定引用。

@comex提出了拥有PinShr的实际原因:应该有可能提供一个从PinShr<'a, Struct>PinShr<'a, Field>的吸气剂; 如果我们使用&PinMut作为无效的共享引脚。 我不知道将需要多少这种吸气剂。


对于您的第一个问题,似乎有一些强有力的论点支持:(a)当前对象安全的任意自我类型的计划,以及(b)在保存引用时能够安全地在共享引用上使用大量现有的API PinShr (或PinMut )。

不幸的是,我们似乎没有一个简单的方法来提供可同时在&mutPinMut ; 毕竟在&mut上工作的大量代码无意使用mem::swap 。 (如我所见,这将是基于!DynSized的解决方案或类似解决方案的主要优点: &mut会变成一种可能固定或可能不固定的引用的类型。我们可以在Pin API中将其作为另一种库类型,但是考虑到我们已经拥有了所有这些&mut方法,这毫无意义。)

有一个反对的轻率论点,即那些想要为&TPinMut<T>做“有趣”事情的类型。 这将很难做到,并非一切皆有可能,而且必须非常小心。 但是我不认为这要胜过赞成的好理由。

在这种情况下(例如,使用此impl Deref ), PinShr<'a, T>应该带有一种安全的方法,可以将其转换为&'a T (保留生命周期)。


我认为我们还必须解决另一个问题:鉴于上述@MicahChalmer注意到的问题, Pin::map和/或drop规则。 我们有两个选择:

  • 声明使用Pin::map投影到公共字段(不取消引用,甚至不隐式)始终是安全的。 这是我对当前RFC的解释。 这将匹配&mut& 。 但是,然后我们在Drop周围遇到问题:现在,给定格式正确的!Unpin类型,我们现在可以编写导致UB的安全代码。
  • Drop附近不要做任何有趣的事情。 然后,我们必须向Pin::map添加大声警告,即使将其用于公共场所也可能导致声音不佳。 即使我们拥有&pin T ,我们也将无法使用它来访问安全代码中的字段。

我可以看到的第二个选项的唯一可能参数是它可能是我们实际上可以实现的唯一参数。 ;)我认为它在所有可能的方式上都逊色-即使&pin内置有内置的

也许有一种方法可以实现第一种选择,但它并不简单,我也不知道如何使其向后兼容:我们可以在Unpin绑定一个Drop ,并添加一个DropPinned没有界限, dropPin<Self> 。 也许Unpin绑定在Drop可以在陌生的路上被强制执行,你可以写impl Drop for S ,但增加了一个隐含的必然S说,它必须是Unpin 。 可能不现实。 :/(我想这也是基于!DynSized的方法工作得更好的点-它会将&mut T变成“可能或可能不会固定”,保留drop声音。)

@RalfJung @MicahChalmer我认为最好记录一下,如果您移出Drop暗示中的字段,则投射到该字段在其他位置的Pin是不正确的。

确实,今天已经存在这样的情况(使用不安全的代码),您可以移出!Unpin类型的字段,并且只要您从未投影到该字段的别针,这是安全且定义明确的。 与Drop的唯一区别在于,移出部分仅包含安全代码。 在我看来,关于笔记Pin::map需要改变注意,这不是安全的,如果你曾经搬出的那场,无论降IMPL的。

在某些情况下,必须安全移出!Unpin类型的字段,因为生成器返回时很可能会移出其字段之一。

我认为最好只记录一下,如果您从Drop impl中移出该字段,那么在其他地方投射到该字段的Pin上是不正确的。

这是第二种选择,使&pin永久字段不安全的操作。
我认为这不是一个小改变。 这从根本上改变了字段中pub的含义。 使用库类型时,我不知道它在其drop impl中的作用,因此从根本上我无法获得对该字段的固定引用。

例如,除非Option明确指出它将永远不会有Drop做任何事情,否则我什至不允许从Pin<Option<T>>转到Option<Pin<T>>滑稽”。 编译器无法理解该语句,因此,虽然Option可以提供适当的方法来执行此操作,但对match进行相同的操作必须保持不安全。

与Drop的唯一区别在于,移出部分仅包含安全代码。

但这是一个巨大的差异,不是吗? 我们可以对不安全代码可以做什么或不可以做什么制定任意规则,但对于安全代码则不行。

在某些情况下,一定要安全地移出!Unpin类型的字段,因为生成器返回时很可能会移出其字段之一。

我想在这种情况下,该字段将是Unpin ? 因此,我们可能会有一条规则说,如果外部结构的公共字段的类型Unpin则该结构的公共字段的Pin::mut是可以的。 不知道这有什么用,但总比没有好。

我想快速重申一下我对&Pin<T>困惑,没有提供比&&T更多的担保。 &&mut&pin分别提供“共享访问”,“唯一访问”和“唯一访问不会移动的值”。 将&&pin理解为“对不会移动的类型的唯一访问的共享访问”会告诉您内存是共享的( &pin的唯一性保证会因共享& ),但是您仍然保留该类型不会被移动的属性,对吗?

我不确定你在问什么或在说什么。 您是否感到困惑,为什么我认为“共享固定”是一种基本的模式/类型状态?

关键是,“共享访问”并不是我自己定义的东西。 共享和协调共享的方式有很多种,例如CellRefCellMutex共享就可以证明这一点。

您不能只是说您正在共享自己拥有的某物(“取消某物的唯一性保证”),并希望该声明有意义。 你说你是如何共享,以及如何确保这不会造成破坏。 您可以“通过将其设置为只读进行共享”,或“通过仅通过同步的加载/存储提供原子访问来进行共享”,或“通过使此借用标志协调要发出的访问类型来共享[在一个线程内]” 。 RustBelt的关键点之一是认识到让每个类型自己定义在共享时会发生什么的重要性。

我无法想到一种使“共享固定”成为共享和固定的正交组成的方法。 也许有一种方法可以定义“共享机制”的概念,然后将其应用于拥有或固定不变的变量,从而产生“(普通)共享”和“固定共享”,但我对此表示严重怀疑。 另外,正如我们所看到的,对于RefCell持平-如果RefCell对共享固定不变式执行的操作类似于它对刚刚共享不变式所做的操作,我们当然不能用& &pin RefCell<T>来证明这一点&RefCell<T> (使用Deref通过) borrow_mut我们可以得到&mut参考,指明没有钉扎发生。

@RalfJung

我们可以将Unpin绑定添加到Drop,并添加一个没有绑定的DropPinned,并且放置Drop需要Pin

这里真的是Drop的定义吗? 考虑它的另一种方法是将责任归咎于mem::swapmem::replace 。 这些操作使您可以移动不属于您的东西。 假设将T: Unpin绑定到_them_?

对于初学者来说,这将解决我指出的drop漏洞-我的Shenanigans无法编译,而且我认为如果没有另一个unsafe我无法使其违反固定承诺。 &mut引用到drop先前固定的值已变得安全,为什么将其限制为仅drop呢?

除非我缺少任何东西,否则我认为这样可以使您随时随地从PinMut<T>借入&mut引用。 (我正在使用PinMut来指代当前称为Pin ,以避免与有关共享引脚的讨论混淆。) PinMut<T>可以实现DerefMut无条件,而不仅仅是T: Unpin

不幸的是,我们似乎没有一种简单的方法来提供可同时在&mut和PinMut;上运行的操作。 毕竟&mut上的大量代码都无意使用mem::swap

PinMutDerefMut PinMut可以解决这个问题,对吗? 关心固定并需要PinMut代码可以安全,轻松地调用在&mut上运行的代码。 负担将转而由_do_要使用mem::swap泛型函数承担-那些泛函函数必须添加Unpin绑定,或者使用unsafe并保重不要违反引脚条件。

现在将这样的界限添加到swapreplace中将破坏向后兼容,可以追溯到第一个稳定版本。 我看不到从这里到达那里的现实方法。 但是,如果仅在1.0天内才知道这件事,我是否会想到其他事情呢?

实际上,没有比@withoutboats说的更好的解决方案了-保持map不安全,并在文档中显示一条消息,警告人们不要移出以前固定在drop的任何字段

我们可以对不安全代码可以做什么或不可以做什么制定任意规则,但对于安全代码则不行。

使用unsafe总是会对周围的安全代码施加规则。 这里的好消息是,据我们所知,如果一个可固定的结构体具有一种将一个引脚投射到私有字段的方法,则只有它自己的drop impl才能使用它来破坏安全代码中的合同。 因此,仍然可以添加这样的投影,并使用完全安全的API呈现该结构的_users_。

Drop的定义真的是这里的问题吗?

指责在某种程度上是任意的,可能会有不同的事情发生,以堵塞健全性漏洞。 但是我们是否同意按照我的建议更改Drop可以解决此问题?

考虑它的另一种方法是将责任归咎于mem :: swap和mem :: replace。 这些操作使您可以移动不属于您的东西。 假设一个T:Unpin bound被添加到他们了吗?

好吧,这会使&mut T !Unpin类型的PinMut 。 您提案中的PinMut<'a, T>可以定义为&'a mut T ,对吧?
这实际上是?Move提议,由于向后兼容性和语言复杂性问题,该提议先前已被放弃。

使用不安全总是会对周围的安全代码施加规则。

我不确定你的意思。 超出隐私边界的情况一定不是这种情况; 不安全的代码不能对其客户端强加任何内容。

好消息是,据我们所知,如果一个可固定的结构体具有将一个引脚投射到私有字段的方法,那么只有它自己的drop impl才能使用它来违反安全代码中的约定。 因此,仍然可以添加这样的投影,并为该结构的用户提供完全安全的API。

是的,类型可以选择加入以声明投影的安全性。 但是例如,借用检查器将无法理解这是字段访问,因此给定PinMut<Struct>您将无法使用此类方法同时向两个不同的字段获取PinMut

但是我们是否同意按照我的建议更改Drop可以解决此问题?

我同意,这样可以解决问题。

我们甚至不再需要PinMut。 您提案中的PinMut <'a,T>可以定义为&'a mut T,对吗?

不,仍然需要PinMut<'a, T>来保证引用对象再也不会移动。 使用&'a mut T您只能相信它在整个生命周期中不会移动'a 。 与今天一样,这仍然是允许的:

``锈
结构X;
impl!取消固定为X {}
fn take_a_mut_ref(_:&mut X){}

fnrow_and_move_and_borrow_again(){
令mut x = X;
Takes_a_mut_ref(&mut x);
让mut b = Box :: new(x);
Takes_a_mut_ref(&mut * b);
}
``

PinMut<'a, T>&'a mut T是安全的,但反之亦然- PinMut::new_unchecked仍然存在,仍然是unsafe

这实际上是?Move提议,由于向后兼容性和语言复杂性问题,该提议先前已被放弃。

据我了解, ?Move提案试图通过更改基本语言规则以禁止上面的代码段(通过使Unpin成为完全不需要PinMut )。 UnpinMove特质。)我没有提出类似的建议-我的建议是从现在的每晚开始,再加上:

  • 添加Unpin绑定到std::mem::swapstd::mem::replace功能
  • UnpinDerefMut impl中删除绑定的Pin (如果发生重命名, PinMut

仅此而已-动作的工作原理等基本语言都没有改变。 我的主张是:是的,这将是一个重大更改,但与更改Drop (反过来仍不如?Move提案那么激烈)相比,它的破坏性影响较小,同时保留了许多的好处。 特别是,这将允许安全的引脚投影(至少对于私有领域,我想甚至对于公共领域也不是很确定),并防止必须编写并行代码才能与PinMut&mut

不,仍然需要PinMut <'a,T>来保证引用对象永远不会再移动。 使用&'a mut T,您只能相信它不会在生命周期内一直移动'a。

我懂了。 说得通。

据我了解,?Move提案试图通过更改基本语言规则以禁止上面的代码段(通过使Unpin成为Move特性)来完全不需要PinMut。

明白了

我的主张是:是的,这将是一个重大更改,但与更改Drop相比,它具有较小的中断影响

您指的是哪个更改? 添加Unpin绑定? 您可能是对的,但我不知道mem::swapmem::replace范围。

特别是,这将允许安全的销钉投影(至少对于私人领域,我想甚至对于公众也不太确定)

我看不出私人与公共在这里如何能有所作为。 公共领域比私有领域如何允许更少?

但是,是的,这似乎总体上是一致的。 Future仍然会花费PinMut因为它必须依靠永不动摇的事物,但是它将有更广泛的可用方法。

但是,兼容性方面是一个很大的方面。 我认为这是不现实的,它将破坏所有调用mem::swap / mem::replace通用代码。 另外,现在不安全的代码可以使用ptr::read / ptr::write来自由实现这些方法; 这可能导致现有不安全代码的无声破坏。 我认为那是不行的。

虽然我们对引进的话题Unpin绑定在mem::swapmem::replace (而不是被关心断裂)。 如果我们假设采用“内置编译器”路由。 是否有可能在mem::forget上引入相同的界限,以确保析构函数针对使thread::scoped听起来不错的堆栈固定变量运行,并且在某些情况下避免“预先拉大裤子”?

请注意,允许在PinBox上使用mem::forget stlll。 与下降有关的新提议保证不是说“事物不会泄漏”。 它说:“如果不先调用drop ,就不会释放任何东西”。 那是一个非常不同的陈述。 此保证对thread::scoped没有帮助。

为了添加上下文,将数据移出实现Future是我通常要做的事情。 如果将来从未轮询完成(在轮询完成之前删除),则经常需要执行清理工作。

因此,即使将文档添加到map ,将现有代码移植到Futures 0.3时,我也肯定会碰上这个地雷。

@carllerche map函数很清楚地说,您不能使用它移动任何东西。 我们不能也不希望防止有人故意离开Pin<T> ,但是您必须摆脱困境(使用不安全的代码)才能做到这一点。 我不会称之为地雷。

那么,您指的是哪个地雷?

@RalfJung我一直在尝试自己找出映射固定引用的界限,并且我认为如果不尽快解决,这将是一个巨大的步枪。 我认为第一个解决方案是可取的,尽管它很复杂。 无法安全地投影到固定字段使得消费者几乎不可能实际使用依赖固定的API而不编写不安全的代码。

如果无法做到这一点,我认为实际上大多数使用固定的可用API必须使用PinShare。 我想这可能不是一个很大的障碍,但是在那种情况下,我仍然不清楚与Unpin的关系。 具体来说:假设我获取了一个共享共享的引用,并获得了对类型字段的引用(在一定的生命周期内)。 一生结束后,我真的可以依靠它不移动吗? 如果该字段是!Unpin ,我可能可以,所以也许不错,只要Pin不能突出显示即可-我主要担心枚举。 除非你是说,即使共享钉扎不能没有固定降安全-在这种情况下,我觉得固定降工作,基本上是钉住发生; 否则,它将成为无法真正安全使用的利基库功能,并且(IMO)不应在核心语言中占有一席之地,即使它确实对期货非常有用。

我还应该提到,到目前为止,我拥有的用于侵入式收集的唯一实用API(我仍然需要弄清楚这些问题)需要比这更强大的保证。 它需要能够保证下降,只要有任何借到收集不叫。 我可以使用GhostCell样式的技术来执行此操作,但是感觉很尴尬,并且要求用户执行手动内存管理(因为如果删除集合中某些内容的后备内存而没有提供令牌,则我们必须泄漏)。 因此,我有点担心自动删除本身似乎很难与以有趣方式使用固定的类型一起使用。

出于好奇:反对添加绑定到DropUnpin的论点是什么? 您提到了向后兼容性,或者是您需要以某种方式自动绑定要删除的对象,但是Drop已经奇怪的类型系统级别限制对于其他特性不存在-为什么这是如此不同? 当然,这不像只让Pin<T>滴水那样优雅,但我们目前无法真正进行更改。 是问题,我们真的不知道,如果你一个类型只有一个掉话做什么Unpin实现,当类型本身!Unpin ? 我猜想在这种情况下在drop实现中抛出异常可能是正确的方法,因为依赖于运行泛型类型的drop任何人都已经需要处理紧急情况。 这意味着在实践中很难使用!Unpin类型,而要避免一群人更新代码以使用新的Drop特性(因此,生态系统将被迫将所有内容转移到thew新版本),但我认为我可以接受,因为它仍然可以保持健全性,并且不会破坏根本不使用!Unpin的代码。 另外,“如果库未升级,您的代码就会慌乱”的事情确实会激励人们继续前进!

实际上,这是我建议的设计:

通过采用Pin的第二种方法扩展Drop特性,如您所建议。 进行专门的默认实现where T: Unpin调用掉落(我想这会通过当前的专门化规则?但是,即使不会, Drop总是可以用特殊情况表示;再加上Drop函数通常是自动生成的)。 现在您具有与我上面建议的完全相同的行为,没有向后兼容性问题,也没有试图笨拙地自动推导边界。

确实存在一个问题,即第三方库必须对其进行升级才能在!Unpin类型中真正有用,但是就像我说的那样,这可以说是一件好事。 无论如何,我不知道有多少drop实现实际上以需要&mut方式来改变其字段-我能想到的大多数方法都可以使用unsafe做一些有趣的事情

这种方法的主要目的是要采用它,必须在稳定Pin之前采用它。 这是我真的希望Pin不被赶到的另一个原因。我认为我们没有充分研究设计的后果。

(我确实看到了另一个潜在的问题: ManuallyDrop和工会通常意味着某人可能已经编写了一个泛型类型的析构函数,该泛型类型确实假定其中的drop实现不会感到恐慌。 ,只是因为它从来没有能够运行(也不会被允许调用任何其它&mut上的一般牛逼的功能,除了不安全特质,承诺不慌实现的)。因为Pin的类型已经必须保证其drop实现在其内存被销毁之前运行(根据新的语义),我相信为此目的使用的ManuallyDrop!Unpin类型首先不能认为现有代码中的图钉是固定的,因此,如果进行这样的假设,那么现有代码应该仍然是正确的;当然,您不应该能够将图钉安全地投影到ManuallyDrop ,因为只有确保您的析构函数在删除之前被调用,它才是安全的。我只是不知道如何将这种情况传达给comp 这似乎可以用于类似的目的吗?这可以完全利用“ eyepatch”东西吗?]。 我不太确定这在语义上会飞,但它可能适用于现有代码的Drop实现。

但是,关于eyepatch的主题……我仍然不确定它的确切形式定义,但是如果总体思路是在T上没有调用任何有趣的泛型函数,也许有人可以进一步利用它? 这意味着,如果在drop_pinned为实现!Unpin类型已实施,眼罩尊重的容器将正常运行。 它似乎有可能,我认为人们可以再编译的时候失败了!Unpin实现类型Drop ,不实现drop_pinned ,不戴眼罩的通用参数,与您使用具有自引用生命周期的非Eyepatch容器时的方法相同。

然而,存在性在任何编译时策略中都存在严重的向后兼容风险。 这就是为什么我认为在运行时失败的解决方案更为现实。 这不会导致运行时失败,也不会出现误报。

编辑:其实,全部刮开上面的:我们真正需要的只是关于析构函数可用的字段,不用担心&mut访问通常,对不对? 因为我们只担心&mut self隐式字段投影。

我可能只是在重新发明!DynSized提议,但从本质上讲:如果通用容器公开了任何关心pin typestate的方法,它们本身必须已经是!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;而言,当不同的typestate发生冲突时,它们不必自动传播。 我确实认识到,从typestate的角度来看,通常&mut TBox<T>T被认为是可以互换的,并且该论点不会扩展到假设的内联存在,但是也许这是DynSize提案的实质内容(不允许安全交换或mem::replace s用于特征对象值,我想是吗?实际上,它们已经不允许使用...但我假设存在将来可能会改变的某些原因)。 这使得编译时解决方案非常简单:对于(间接Box或原始指针),任何结构都不会安全地传递Pin访问权限,除了&作为共享图钉之外,但无论如何都无法移出& ,或者如果您决定采用“无法移出特征对象”解决方案,还可以沿&mutBox传递地检查字段,并且可以(在可见性上)访问已知的!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>执行操作。您通常也希望将它们与PinBoxPinTypedArena或在任何时候?Unpin边界看起来很自然)。

我有一个新设计,我认为它不像旧设计那样糟糕: https@RalfJung建议的PinDrop特性,但仅适用于既具有自定义放置又要投影字段的类型。

类型显式地选择进入投影字段(对应于导出PinFields特性),这类似于用“现代” Rust编写的代码; 但是,它对“旧版” Rust中的代码没有其他要求,而是选择仅对PinFields任何给定派生支持投影到深度1。 它还不会尝试通过引用移动Pin ,我认为这是一个不做的好主意。 它支持结构和枚举,包括任何Rust应当能够提供的不相交分析,方法是生成具有相同字段和变量的结构,但用Pin 'd类型替换对类型的引用(这应该是琐碎的)进行更改时将其扩展到PinPinMut )。 显然,这不是理想的方法(尽管希望优化器可以摆脱大部分方法),但是它的优点是它可以与rowck和NLL一起使用,并且可以轻松地与枚举一起使用(与生成的访问器相对)。

安全性参数的工作方式是,确保根本不为该结构实现Drop,或者确保如果为该结构实现Drop,那么这是一个简单的实现,仅调用PinDrop(采用Pin的Drop版本))。 我认为,这排除了投射固定字段的所有健全性问题,并带有一个问号:我的主要关注点是找到一个很好的论据,说明为什么在完全相同的容器(即深度1的字段)上不相交的字段可能使彼此的无效没有销钉突出的销毁器中的销钉将已经无效。 我想我可以证明这一点,如果我们可以证明,你也可以做同样的事情,如果他们在不同的PinBoxes举行,这意味着他们居住的地方是其安全性合同的一部分; 这意味着它们的析构函数在隔离时是不安全的,并且在模块外部构造它们将需要不安全的代码。 换句话说,它们的正确性取决于容器类型的实现,这意味着可以向我们要求更多的析构函数,而不是我们要求任意安全代码。

@pythonesque我并没有真正遵循您上面关于DynSize ,但是我认为现在这已经过时了吗? 因此,我只对您的最新文章发表评论。

总而言之,您是说投影到struct / enum的字段(包括pub字段)通常是不安全,但是类型可以通过不实现Drop选择加入安全字段投影Drop 。 如果类型需要析构函数,则必须使用PinDrop而不是Drop

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

我们已经有类型检查$$$ Drop来拒绝移出字段,因此也检查Drop来拒绝通过&pin投射似乎并不现实。 当然,如果存在PinDrop ,“移出域”检查仍将拒绝,而在这种情况下将允许投影。

编译器将对PinDrop施加与对Drop相同的限制,并确保类型不会同时实现DropPinDrop 。 生成滴胶时,它将调用该类型已实现的任何类型的Drop

这是对提案的总结吗? 我不理解的部分是您的最后一段,您能否举一个例子说明您担心的是什么?


现在,我当然想知道这里的证明义务。 我认为最简单的方法是说PinDrop实际上是正式存在的主要且唯一的析构函数,而impl Drop for T实际上是impl PinDrop语法糖。调用不安全的方法PinMut::get_mut ,然后调用Drop::drop 。 这是自动生成的不安全代码,但是,因为固定是“本地扩展”(即,它与现有不安全代码向后兼容),所以如果给定类型不关心固定,则该不安全代码始终是安全的。

从形式上来说,如果类型的作者不关心固定,则类型具有“默认固定不变”。 使用该默认固定不变量的类型将自动Unpin 。 为具有自定义不变式的类型编写impl Drop断言该类型使用“默认固定不变式”。 这有点可疑,因为感觉有点像这里有证明义务,但没有unsafe可以警告这一点-确实这不是完美的,但是向后兼容很重要,那么您可以做什么呢?做。 这也不是灾难,因为有人可以说这实际上意味着什么,它改变了在其他地方对不安全代码产生的证明义务,从而扩大了这种不安全代码必须与“默认固定不变”一起使用的范围。 如果此模块中的其他地方没有不安全的代码,则一切正常。

我什至可以想象,除非T: Unpin ,否则我们可以对impl Drop for T添加一个lint,可能仅限于定义类型的模块中包含不安全代码的情况。 那将是一个教育人们关于这个问题的地方,并鼓励他们要么unsafe impl Unpin (正式宣称他们使用默认的钉扎不变式),要么鼓励他们选择impl PinDrop

@RalfJung

这是对提案的总结吗?

是的,或多或少(我认为您的建议实际上比我的建议要雄心勃勃,因为它似乎建议在未实施Drop的情况下自动执行投影,我认为这可能是库的前向兼容性问题;但是也许有某种解决方法那)。

我不理解的部分是您的最后一段,您能否举一个例子说明您担心的是什么?

广义地说:我担心两个直接位于同一结构上的字段会受到影响,其中一个字段会在调用其Drop时发生变异(可能依赖于drop order来确保安全),从而违反了固定变量的方式。其他字段,但保留其结构完整性(因此可让您目睹违反固定不变的行为)。 这显然不能使用完全安全的代码(否则它将被dropck拒绝,等等),因此我所关心的只是某些假设情况,即当将字段固定在单独的位置时,可以安全地运行字段类型的析构函数结构,但将它们固定在同一结构中时并不安全。 我希望没有这样的情况,除非存在一个共享的不变量,该不变量包括字段所包含的结构。 如果存在这样的不变量,我们知道它必须意识到它的组件没有正确地遵守自定义不变量,因此我们可以将代码归咎于模块中的某个地方。

我认为最简单的方法是说PinDrop实际上是正式存在的主要且唯一的析构函数,而impl Drop for T实际上是impl PinDrop的语法糖,它调用了不安全的方法PinMut :: get_mut然后调用掉落::掉落。

同意不幸的是,这种方法并不适合当前的解决方案,因为它试图在库中执行操作。

从形式上来说,如果类型的作者不关心固定,则类型具有“默认固定不变”。 使用该默认固定不变量的类型将自动取消固定。 为具有自定义不变量的类型编写impl Drop可以断言该类型使用“默认固定不变量”。 这有点令人费解,因为感觉有点像这里有证明义务,但是对此并没有不安全的警告-确实这不是完美的,但是向后兼容很重要,因此您可以做什么。

是的,这大概是我的主意...我只是担心我对这个“模块中不安全代码”保证的正式形式化表示不了解。 我确实喜欢您的皮棉想法(实际上,我喜欢使PinDrop吸引更多人的任何事物!),但我认为unsafe impl Unpin可能经常会出错,因此建议不要这样做。至少对于具有通用公共字段的类型而言(但再次,对于不具有此类公共字段的结构,实际上通常会是正确的……例如,在很大程度上,标准库是正确的)。

我写了一个#[derive(PinnedFields)]如何工作的示例: https :

我见过人们断言,这样的派生词“不是声音”,但事实并非如此。 您将需要使用不安全的代码来执行与此派生程序生成的代码相冲突的操作-也就是说,这会使其他不安全的代码不合要求(我认为该代码-在?Unpin数据周围移动-是您可以/应该始终避免)。

编辑:好的,实际上阅读了有关析构函数的最后几篇文章。 将过程。

@withoutboats是的,我想您已经看到了,但是问题是,正确的!Unpin类型中的不安全代码可以由派生PinFields的类型上的安全析构函数使之无效(因此除自动生成的内容外,模块中没有用于导出PinFields的类型的不安全代码)。 那是有问题的部分。 不过,请看一下我链接的设计(忽略了创建单独结构而不是派生单个访问器的风格选择,这只是我试图通过借阅检查器使其支持尽可能多的用例)。 我担心了一段时间,但是现在我很确定#[derive(PinFields)]仍然可以工作,它只需要注意确保没有直接实现Drop

我还想提出我一直在思考的另一点(我还没有看到可以在任何地方直接解决?):我认为可以使Pin的可用性更高,并更好地集成到现有代码中,将坚决归结为基本上所有指针类型都是Unpin 。 也就是说,使&mut T&T*const T*mut TBox<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 '参数作为&mut )。

我可能会缺少某种原因,为什么这种可传递的固定会是一个好主意,但是到目前为止,我还没有找到任何理由。 我可能担心的唯一可操作的事情是,实际上是否可以实现具有自动特征的行为-我认为可能会实现,但是在某些情况下,人们使用PhantomData<T>但实际上拥有一个指向T指针,将其更改为PhantomData<Box<T>> 。 通常,我们认为它们在语义上是完全相同的,但是通过固定并不完全正确。

@pythonesque “一种新语言”的术语对我来说

  • 默认情况下, #[derive(PinFields)]生成无操作的Drop impl。 这样可以确保您永远不会访问析构函数中的固定字段。
  • 可选属性将此Drop impl更改为调用PinDrop::pin_drop ,您应该实现PinDrop

这是正确的吗?


我还相信,只有当我们扩展Pin的担保以支持侵入式收集时,整个事情才有意义。 这是否与您对@RalfJung和@pythonesque的理解一致?


所有这一切都是令人沮丧的,因为似乎很明显的是, Drop应该使自己受Pin ! 进行更根本性的改变(可能是时代性的)似乎很有吸引力,但是我看不出一种破坏性不大的方法。

这是对固定字段+丢弃可能导致的不健全访问的示例: https ://play.rust-lang.org/?gist=8e17d664a5285e941fe1565ce0eca1ea&version=nightly&mode

Foo类型将内部缓冲区传递给某种外部API,该API要求该缓冲区保持在原处,直到显式取消链接为止。 据我所知,在@cramertj建议的约束下这是合理的,在创建Pin<Foo> ,可以确保在调用Drop之后,它不会移动(带有前提是它可能会泄漏,并且永远不会调用Drop ,但是在那种情况下,您可以保证它永远不会被移动)。

然后,类型Bar在实现Drop过程中通过移动Foo打破了这一点。

我使用与Foo非常相似的结构来支持通过DMA进行通信的无线电外围设备,我可以拥有一个带有内部缓冲区的StableStream ,该内部缓冲区由无线电写入。

@withoutboats

这是正确的吗?

是的,除了它实际上不会生成无操作的Drop impl(因为在Rust中未实现Drop类型通常效果更好)。 相反,它试图断言Drop尚未使用某些可疑的库功能实现(它可以稳定运行,但在专业化条件下会中断-我认为有一个变体应该在专业化条件下工作,但现在不行,因为相关常数的问题)。 如果它具有语言功能,则实施此功能将非常容易。

我还相信,只有当我们扩展Pin的支持范围以支持侵入式收集时,整个事情才有意义。 这是否与您对@ralfj和@pythonesque的理解一致?

不,不幸的是,事实并非如此。 上面链接的反例与侵入性集合的额外保证无关。 Pin 'd类型已经必须能够假设即使没有保证也不会再次使用,因为如果从固定引用后面的值上两次调用方法,则该值具有无法知道它是否在两个调用之间移动。 是需要的额外保障,使其可用于侵入集合添加有关不必调用额外的东西drop的内存被释放前,但即使没有这样的保证drop仍然可以对一些所谓的目前已固定(例如,在PinBox的后面)。 如果被丢弃的对象恰好包含内联字段,并且我们允许从被丢弃的对象投射到那些字段,则外部类型的析构函数仍然可以移动内部字段,然后再次将其固定(通过移出(例如,在PinBox中的字段值),然后在其上调用期望从其固定前的引用开始仍然有效的方法。 我真的没有办法解决它。 只要您可以实现drop ,任何内联!Unpin字段都可以遇到此问题。 这就是为什么我认为最糟糕的解决方案是,如果人们想要固定以正常工作,则不要让他们实现drop

所有这一切都令人沮丧,因为似乎很清楚的是Drop只能靠Pin自己! 进行更根本性的改变(可能是时代性的)似乎很有吸引力,但是我看不出一种破坏性不大的方法。

是的,这确实很烦人。。。我对此大约一个星期感到非常闷闷不乐。 但是正如拉尔夫(Ralf)所指出的那样,在有人想出这一点之前,我们将不得不再等三年才能获得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时(可能出于安全考虑而依赖于drop order之类的东西)会以违反固定另一个字段不变式的方式来改变另一个字段,但保留其结构完整性(因此可以让您目睹违反固定不变的行为)。

但这目前已经是非法的。 您不得违反他人的不变式。

但我认为不安全的隐式Unpin可能经常会出错,以至于建议它是一件好事,至少对于具有通用公共字段的类型而言

我认为大多数情况下这实际上是正确的-我希望大多数人不会为Pin<Self>提供任何访问器,在这种情况下,他们很可能使用默认的固定固定变量,因此对unsafe impl Unpin

我认为使Pin更加有用并且可以更好地集成到现有代码中的一些事情,是要坚决放弃实质上所有类型为Unpin的指针。 也就是说,使&mut T,&T,* const T,* mut T,Box,依此类推,所有T都应视为Unpin。要取消固定,当您认为无法用固定销将固定销固定到盒子内部时,这很有意义>。

我同意这可能会发生(另请参阅我的评论,网址为https://github.com/rust-lang/rust/pull/49621#issuecomment-378286959)。 目前,我觉得我们应该稍等片刻,直到对整个固定操作都更有信心为止,但是实际上,我认为在指针指针之外执行固定操作没有多大意义。
我不确定对原始指针执行此操作的感觉如何,我们通常对它们的自动特征极为保守,因为它们以各种疯狂的方式使用。


@withoutboats

我写了一个有关#[derive(PinnedFields)]如何工作的示例: https :

@pythonesque已经说过这一点,但这只是要清楚:该库是不完善的。 与@MicahChalmerdrop问题一起使用时,我可以破坏实际上依赖于钉扎的任何钉扎类型。 这独立于有关丢弃的附加保证。 让我知道是否仍然需要一个示例。 ;)

@RalfJung

该图书馆是不健全的。

为了澄清,派生只是unsafe ,不能与手动Drop impl结合使用。 通过使用派生,您保证不会做Drop实现中列出的任何坏事。

https://github.com/rust-lang/rust/pull/50497更改了钉扎API,最重要的是,它将Pin重命名PinMut以腾出空间来添加共享的Pin将来。


为了明确起见,派生仅是不安全的,不能与手动Drop impl结合使用。

同意,以这种方式使其不安全将起作用。 尽管测试使它看起来像当前它实际上没有标记为不安全?

@RalfJung对,我现在不知道。 我刚刚想到的另一个选择是让“安全”版本为该类型创建Drop实现,从而阻止其他手动提示Drop 。 可能会有unsafe标志将其关闭。 WDYT?

@cramertj是的,这也应该起作用。 但是,它会产生副作用,例如限制性更强的dropck以及无法移出字段。

@pythonesque和我周一打电话来讨论这个问题。 这是一个摘要。

我们得出的结论是,“ Drop的“正确”行为可能是凭空取得的。 但是,过渡到这一点令人生畏。 尽管我相信以某种形式进行版本更改是可能的,但这将是极具破坏性的。

向后兼容的更改只是使可以“固定投影”以实现Drop类型某种程度上不一致。 在他的存储库中, @ pythonesque通过从一系列复杂的特征生成impls来实现此目的。

可以想象一个内置的表单会更简单一些:

  • 标记特征,我们称它为PinProjection ,控制是否可以固定投影类型。 同时实现PinProjectionDrop是不连贯的(通过编译器内置的魔术)。
  • 另一个特征PinDrop扩展了PinProjection来为Drop提供替代析构函数。

用于生成销钉投影方法(如@pythonesque)PinProjection impls。

@withoutboats

我们得出的结论是,“正确”的行为可能是Drop凭一己之力获得的。 但是,过渡到这一点令人生畏。 尽管我相信以某种形式进行版本更改是可能的,但这将是极具破坏性的。

只是想知道,如果我们有一天想做这样的事情,您提出的与未来兼容的建议是吗?

例如,假设我们决定...

  • 使Drop神奇地接受固定形式和非固定形式,或者
  • 作为自动升级到Rust 2021的一部分,重写每个人的Drop签名:)

(我没有提出任何一个建议,但是如果没有具体示例,似乎很难回答这个问题。)

@tmandry本质上就是这个想法。

最大的问题是通用的Drop实现会移动type参数:

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 ? 似乎没有必要。

也可以通过说PinProjectionDrop不兼容,除非类型为Unpin (或者找到其他方法来消除所有这些新的区别/限制),从而使这一点变得更聪明。适用于Unpin类型)。

@RalfJung我们希望PinDropDrop以某种方式互斥。 最终,有几种方法可以以不同的权衡取舍。 我认为我们需要一个RFC,因为它的变化不大。

@withoutboats同意。 我认为排他性可以通过对PinDrop的特殊处理而产生,但是显然有几种方法可以实现。 我认为如果不必关心Unpin类型,这将非常有用。 加上无条件地使所有指针类型Unpin这将可能有助于减少人们甚至必须了解这一点的情况。

@withoutboats还值得一提的是,我能想到的主要实例是人们想要在析构函数中的Vec中移动通用数据的位置(我之所以选择Vec是因为IIRC人们愿意在Vec实现实际上关心Pin ,这意味着它不能无条件地实现Unpin ),实际上是&mut Vec<T>Vec<&mut T> 。 我主要是提出来的,因为这些情况可能会被皮棉@RalfJung建议,并且希望人们因此能够在这些情况下轻松地移至PinDrop ,而不是unsafe impl Unpin (理论上,他们总是可以通过在类型中将TUnpin绑定来实现前者,但这对库的客户来说将是一个重大突破)。

同样,值得注意的是,尽管这确实增加了一些我们不想要的特征,但是这些特征几乎永远不会出现在任何人的类型签名中。 除非您正在编写#[derive(PinFields)]宏,否则特别是PinProjection是一个完全没有意义的界限,因为编译器将始终(我相信)能够确定PinProjection是否可以保存弄清楚类型上的字段是什么,它唯一有用的就是投射字段。 类似地, PinDrop基本上不需要绑定任何对象,原因与Drop几乎不用作绑定相同。 甚至特征对象也应该基本上不受影响(我猜除非有一天我们会获得“关联字段”,但是有了这样的新功能,我们可以强制要求具有关联字段的特征只能在PinProjection类型上实现,这可以很好地解决这个问题)。

@RalfJung

好吧,我认为我们最终想这样做。 兼容性问题如何?

我想这不只是添加一个取消实现SendSync ,其区别在于,这两种类型都不会对核心语言产生相同的影响。 完全自动执行此操作似乎类似于Rust过去对待Copy (如果类型仅包含Copy类型且未实现Drop类型始终Copy Drop )最终进行了更改,以使其更加明确,因为人们大概不喜欢添加一个特征( Drop )可能会破坏通用代码,而又没有意识到这一事实(因为整个一致性的关键是额外的特质的实现不应破坏下游的板条箱)。 这似乎几乎相同,只是使用PinProjection而不是Copy 。 我实际上很喜欢旧的行为,我只是认为很难证明PinProjection不能用Copy这样的方式工作。

但这目前已经是非法的。 您不得违反他人的不变式。

是的,我对这个问题的思考越深,似乎就不太可能成为问题了。

我认为大多数时候这实际上是正确的

好吧,是的,但这仅是因为大多数类型不实现任何需要Pin或将其通用字段公开为公共的方法。 尽管后者不太可能改变,但前者则不会改变-我希望至少有一些当前!Unpin的stdlib类型添加pin投影方法,此时unsafe实现会不再有效。 因此,对我来说,这似乎是一件非常脆弱的事情。 而且,我担心人们不得不编写的“样板”不安全代码数量越来越多。 我认为SendSync界线是正确地执行unsafe impl的频率,与执行不正确的频率差不多,而Unpin阴谋则更加隐蔽通常,正确的版本对T没有任何限制。 因此,将人们引向PinDrop似乎非常可取(不过,我确实理解您为什么对原始指针类型保持谨慎态度。我只是担心已经- unsafe代码会甚至更可能不加思索地执行unsafe impl ,但是我考虑的越多,原始指针的默认值似乎就越正确,并用您的Lint进行标记将很有用)。

我认为,如果不必关心Unpin类型,这将非常有用。

我有点同意,但是由于就像我说过的那样,人们不会将PinProjection用作实际界限,所以我不确定在实践中这有多重要。 已经有DerefMut PinMutDerefMut实现,其中T: Unpin因此您今天不会从中获得任何好处。 在编译器中实现的规则(用于投影)可能会将PinMut::new转换为针对Unpin类型的&pin借入,但实际上与字段投影无关。 而且,由于派生PinProjection不需要为其字段输入PinProjection ,因此,您不需要仅满足另一个类型的derive 。 真的,有什么收获? 它真正会让您做的唯一一件事就是实现Drop并同时导出PinProjections ,但是我们始终希望人们尽可能地实现PinDrop ,这样成为净负面IMO。

我选择Vec是因为IIRC人员希望在Vec上实现实际上关心Pin的方法,这意味着它不能无条件地实现Unpin

嗯,我不喜欢那样。 如果Box无条件是Unpin ,我觉得Vec应该相同。 这两种类型通常在所有权上几乎相等。

此外,我们还必须对drop保证金保持谨慎; 例如,在紧急情况下, Vec::drain可能导致泄漏。

这似乎几乎相同,只是使用PinProjection而不是Copy

哦,现在我明白了你的问题。 我实际上不是在谈论自动派生PinProjections因为我的提案没有这样的特征-但实际上我的提案的一个后果是,如果您愿意,添加Drop是一项重大突破。有公共场所。

它真正让您做的唯一一件事是同时实现Drop和派生PinProjections,但是无论如何,我们始终希望人们能够实现PinDrop,因此这将是一个负面的IMO。

这实际上就是重点。 人们不必在乎所有这些固定的东西,那就更好了。

澄清问题:在您和@withoutboats的提案中, PinDrop是一个lang项目,被编译器视为Drop的替代项,还是derive(PinProjections)使用的特征Drop

另外,我们必须注意掉线保证; 例如,Vec :: drain可能会在发生紧急情况时导致泄漏。

好吧,是的,但是实际上并没有释放任何内存,对吗? 需要明确的是,其实我更喜欢,如果Vec无条件履行Unpin因为这将使它更容易为人们只需切换到PinDrop ,但肯定是谈谈在关于让您固定到支持元素的一些固定讨论。 一旦您开始谈论固定字段,您就会发现您不能总是将Vec变成适当的Box<[T]>然后固定它,因此它实际上可能有一些价值这一点(尽管显然您也可以添加PinVec类型而不是Vec类型,就像Box )。

这实际上就是重点。 人们不必在乎所有这些固定的东西,那就更好了。

嗯,从我的观点看,最初是正确的,但从长远来看,我们希望将尽可能多的人迁移到默认值PinDrop ,尤其是当类型实际上困扰到#[derive(PinProjections)] (如果类型为Unpin出于任何目的,甚至也不会出于任何目的,它只会在生成的代码之类的东西中发生)。 然后(也许在&pin已经使用了一段时间的语言之后),您可以进行一个划时代的更改,将Drop切换到DeprecatedDrop 。 对于Unpin类型,只需将类型签名从&mut更改&mut pin解决所有问题,因此也许并不是真正需要的。

澄清性问题:在您和@withoutboats的建议中,PinDrop是lang项目,并被编译器视为Drop的替代品,还是只不过是generate(PinProjections)用于实现Drop的特征?

前者。

整个Drop讨论似乎代表原始RFC进行了相当激烈的监督。 打开新的RFC讨论这一点是否有价值? 很难理解这些关注点是什么,这些关注点比其他关注点更有害,确切地说,我们需要向语言中添加的机械要比我们最初想象的要多得多,我们应该期待多少破损(以及是否在最坏和最理想的情况下,该版本可以缓解该问题),我们如何过渡到可以成功实现Drop的所有功能,以及是否可以通过所有其他方式实现这些目标并仍然实现2018年的目标(例如https: //github.com/rust-lang/rfcs/pull/2418建议对所有公共API隐藏Pin,以免影响稳定性。

@bstrie我认为只有一个主要问题,但这是一个相当重大的问题。 批准RFC的前提是最终的&pin mut类型可以在某种程度上使这种代码安全:

struct Foo<T> { foo : T }

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

这种代码的安全性很重要,因为在实践中,固定引用在安全代码中不是真正可组合的,因此例如要实现任何期货组合器,都需要使用unsafe 。 当时,人们认为在大多数情况下可以取消使用unsafe的用途,因此这并不是什么大问题。

不幸的是,这是否是安全依赖于Drop实施Foo 。 因此,如果我们要支持安全的字段投影(通过语言功能,自定义#[derive]或任何其他机制),我们必须能够保证如此糟糕的Drop实现可以'不会发生。

看起来效果最好的替代方法可以使上面的代码在不使用unsafe (仅在结构顶部添加#[derive(PinProjections)] ),并且不会破坏任何现有代码。 即使Pin已经稳定下来,它也可以向后兼容地添加,甚至可以作为纯库添加(符合人体工程学原理)。 它与用于生成访问器的#[derive]宏以及最终的&pin本机引用类型都兼容。 尽管它需要添加至少一个新特征(可能还需要添加两个,具体取决于Drop的固定版本的实现方式),但是它们永远不需要出现在where子句或特征对象中的任何位置,并且新版本的drop只需要针对当前具有Drop实现并希望选择大头针投影的类型实现。

仅仅因为一个解决方案似乎没有太多缺点并不意味着它不是一个实质性的改变,所以我认为我们几乎可以肯定会为此提案创建一个RFC( @withoutboats ,我在电话中讨论过这样做) 。 最好单独隔离它,因为它是完全向后兼容的。 不过,就我个人而言,我认为推动这种变化比摆脱别处固定更为有意义。

大多数人对公共API中的PinMut感到担心的是,它要么需要unsafe要么需要遍历Unpin边界,因此该建议解决了这个问题。 rust-lang / rfcs#2418中讨论的替代方案似乎更具争议性,无论是在其希望避免固定的方式的实际机制上(涉及在公共API和文档中出现的各种其他特征的泛滥),还是解决方案的整体复杂性。 确实,即使完全解决了固定问题,我认为仍有很多人认为该RFC无法充分解决这些问题,因此,我认为至少有相当大的机会最终会增加一个添加安全引脚投影的RFC被接受之前。

确实,钉扎技术还处于初期阶段(我知道我抱怨它似乎过快被稳定下来了),但是我认为缺乏安全的野外投射是阻碍人们完全使用它的最后一个主要因素。 。 为了完整起见,以下是我看到人们在固定时提出的所有问题,他们提出的解决方案以及该解决方案是否与现有的Pin类型向后兼容(如何粗略地,有偏见地有争议的我认为目前是这个问题):

  • 这一个(我们可以用安全代码完成投影场吗?)。 由即将到来的RFC解决(将详细说明可能的实施策略,以及我们考虑的其他替代方案以及为什么必须丢弃它们)。 提议的分辨率的所有变体都向后兼容。
  • 用手动析构函数固定!Unpin类型的值是否意味着额外的保证(即在调用析构函数之后,该值的后备内存才无效),又称为“侵入性集合的更改”?

    仍未解决,主要是因为它可能会破坏建议的堆栈固定API; 如果可以使保留此保证的堆栈固定API与异步/等待一起使用,则大多数利益相关者似乎愿意接受这一点(IIRC有人已经尝试使用生成器来解决此问题,但使用ICE进行了rustd操作)。

    与旧的保证不向后兼容; 现在要求不安全的代码强制执行保证与两种可能的结果都兼容,但前提是不允许不安全的代码依靠其强制执行来确保正确性。 因此,在固定销钉之前,这肯定需要一种方法或另一种方法。

    幸运的是,钉住Future s的用户除了堆栈钉住API的形状外,还可以忽略它。 基于闭包的堆栈固定API与任何一种分辨率都兼容,因此未使用async / await的Future用户今天可以使用基于闭包的API,而无需等待决定这一问题。 该决定对想将钉子用于其他目的(例如侵入式收藏)的人们影响最大。

  • 原始指针应该无条件地Unpin吗? 仍未解决(我是唯一提出该提议的人,而我仍然提出50-50)。 不会向后兼容; 我主要因为这个原因而将其标记为有争议的。
  • 应该像无条件地将Vec类的标准库类型设为Unpin ,还是应该添加Pin字段访问器? 仍未解决,可能需要逐案解决。 对于任何给定的安全包装器类型,添加访问器或无条件使Unpin都向后兼容。
  • 应该删除PinMut::deref吗? 答案基本上似乎是“否”,因为保留它的好处似乎远远大于弊端,而且对于最初使人们想要它的情况(特别是Pin<RefCell<T>> ),似乎存在变通方法。 更改它会向后不兼容。
  • 我们应该如何在短期内提供现场访问者(忽略掉线问题)? 仍未解决:到目前为止提供的两个选项是https://github.com/withoutboats/derive_pinnedhttps://github.com/pythonesque/pintrusive。 分辨率完全向后兼容,因为在放置更改之后,可以完全在库代码中的宏中正常进行解析。
  • 从长远来看,我们应该如何提供字段访问器(即,应该有自定义的&mut pin&pin Rust类型?应如何重新借用?)。 仍未解决,但向后兼容所有其他提议的更改(就我所知),并且显然可以无限期使用。
  • 安全的类似引用的类型应该无条件地设为Unpin吗? 似乎有待解决(是的,应该)。 分辨率完全向后兼容。
  • 除了唯一的Pin Pin类型外,我们是否还应该使用共享的&Pin因为这是对参考)? 解决方案是将Pin更改PinMut ,并为共享案例添加Pin类型。 这不是向后兼容的,但是已经进行了重大更改(从PinPinMut ),并且我很确定这已经被有效接受了。

我认为几乎可以解决这个问题。 如您所见,所有这些变量(除了原始的指针和deref决策,后者似乎在此时都已得到很大解决),即使销钉今天已稳定,它们也具有一些向后兼容的路径。 更重要的是,我们可以解析字段预测这一事实意味着,在您的API中使用固定类型并不是注定的决定,您以后会后悔。

除了上述问题之外,还有其他提案旨在从更根本的角度重新考虑固定。 我认为我最了解的两个是@comex建议,以使!Unpin类型!DynSized ,以及steven099(关于内部;抱歉,我不知道Github名称)建议具有一个新的未定尺寸的Pinned包装器类型,该包装器使内部构件无法移动(有点像ZST包装器)。

!DynSized选项是一个相当保守的功能(在某种意义上,Rust已经具有类似的可用特性),其优点是处理不透明类型可能已需要它。 从这个意义上讲,它可能比提议的Drop更改更具侵入性。 它还具有很高的优势:它可以自动用Drop解决问题,因为!Unpin类型将是!DynSized ,因此一个人将无法移出它们。 只要T!DynSized ,这将使&mut T&T自动用作PinMutPin !DynSized ,所以您会不需要大量使用&mut T的类型和方法的固定版本,通常可以使其正常工作(如果将它们修改为不需要时不需要DynSized绑定) )。

主要缺点(除了通常围绕?Trait )似乎是永远无法移动!Unpin类型,这与当前固定类型的情况大不相同。 这意味着在不使用引用的情况下构成两种固定类型实际上是不可能的(据我所知,无论如何),而且我不确定如何或是否对此有任何建议的解决方案。 IIRC旨在与&move提案一起使用,但是我不确定该提案的预期语义是什么。 我(根据建议)也不了解如何使用安全的字段投影,因为它依赖于不透明的类型。 似乎您通常必须使用很多不安全的代码才能使用它。

未调整大小的Pinned<T>类型在本质上有点相似,但希望通过允许您将类型包装在使其不动的ZST中(有效地执行未调整大小)来解决上述问题。 Rust目前没有任何可比的东西: PhantomData实际上不包括该类型的实例,其他动态大小的类型生成胖指针并仍然允许移动(使用依赖size_of_val API ?DynSized打算解决的问题,因此该建议可能会再次搭载该特性。 在我看来,如果您允许安全投影,该提案实际上就可以解决Drop问题,并且它似乎也与Deref不兼容,所以对我来说,优于Pin不太清楚。

如果可以使保留此保证的堆栈固定API与异步/等待一起使用,则大多数利益相关者似乎愿意接受这一点(IIRC某人已经尝试使用生成器来解决此问题,但使用ICE时会生锈)

作为参考,这可能是参考https://github.com/rust-lang/rust/issues/49537 ,这是基于基于嵌套生成器的堆栈固定API的尝试。 我不确定即使解决了ICE,那里的生命周期是否也会起作用。

仍未解决,主要是因为它可能会破坏建议的堆栈固定API; 如果可以使保留此保证的堆栈固定API与异步/等待一起使用,则大多数利益相关者似乎愿意接受这一点(IIRC有人已经尝试使用生成器来解决此问题,但使用ICE进行了rustd操作)。

我看到基于闭包的堆栈固定API是对此的一种解决方案,它具有将来的途径(一旦&pin是一种语言原始语言),便可以提供更符合人体工程学的功能,并由编译器进行检查。 @cramertj也提出了这种基于宏的解决方案

是否应无条件取消固定原始指针? [...]不会向后兼容; 我主要因为这个原因而将其标记为有争议的。

您为什么认为添加impl Unpin for Vec<T>是向后兼容的,但对原始指针却不兼容?

主要的不利因素(除了围绕?Trait的常见问题外)似乎是!Unpin类型永远无法移动,这与当前使用pinned类型的情况大不相同。 这意味着在不使用引用的情况下构成两种固定类型实际上是不可能的(据我所知,无论如何),而且我不确定如何或是否对此有任何建议的解决方案。 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 )访问它,因为后者会允许使用swapreplace 。 而是,用户代码直接与&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> (访问ab )。 同样在这里,这看起来与现有的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) }
    }
}

但是,就像现有设计一样,该宏将需要阻止用户为TwoGenerators实施Drop TwoGenerators 。 另一方面,理想情况下,编译器将允许我们impl Drop for Pinned<TwoGenerators> 。 当前,它以“无法专门执行Drop的实现”的错误拒绝了此操作,但是可以更改。 IMO这比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在来自其他语言的人那里看起来更好,在这些语言中,实现自指值很容易并且很普遍。

将来,我会设想,本机不可移动类型将解决这两个问题,并且大多数代码将永远不需要使用“ pin”一词。 (尽管Pinned可能仍然有一些用例,对于复制省略不够好并且您确实需要初始可移动状态的情况。)相反, Pin烘托了a的​​概念。将“尚未固定”状态分离到使用该状态的每个特征的设计中。 内置的&pin会将其烘焙到语言中。

无论如何,这是上面Pinned设计的游乐场链接

[1] ...尽管我们可能想要一个新的特征ReallySized (名称不太傻),用于静态大小的类型,但可能是可移动的,也可能是不可移动的。 问题是,无论如何,这里都会有些混乱,因为在支持不定大小的右值的情况下,许多当前采用Sized参数的函数可能与不定大小但可移动的函数一样好。 我们将更改现有函数的界限,并建议用于将来函数的界限。 我声称,将来的版本甚至可能值得更改函数泛型的默认值(暗示),尽管这样做会有一些缺点。

[编辑:固定代码示例]

@RalfJung

我将基于闭包的堆栈固定API视为对此的解决方案,它具有将来的途径(一旦&pin是一种语言原始语言),就可以更符合人体工程学,并由编译器进行检查。 @cramertj也提出了这种基于宏的解决方案。

我相信基于闭包的代码不能与async / await一起正常工作,因为您无法从闭包内部产生收益。 不过,基于宏的代码很有趣。 如果那真的很安全,那将非常巧妙。 我不认为它起初会起作用,因为在一个范围下降期间出现的恐慌可能导致其他范围的泄漏,但是显然这已由MIR解决了吗?

我也不确定“在释放内存之前运行析构函数”保证与投射固定字段的能力之间的相互作用。 如果顶级丢包恐慌了,我以为该值中的投影固定字段将不会运行它们的丢包。 但是,在Rust游乐场上,即使在顶级类型的紧急事件发生之后,该类型的字段实际上似乎仍然有它们的液滴运行,这非常令人兴奋! 该保证书实际上记录在任何地方吗? 似乎有必要使堆栈固定与引脚投影配合使用(或者更重的东西,例如PinDrop总是在紧急情况下中止,这似乎是不希望的,因为这会在Drop和PinDrop之间引入功能差异)。

您为什么认为为Vec添加impl Unpin向后兼容,但对原始指针不一样吗?

我明白你的意思了:有人可能会依靠自己拥有的Vec<T>字段中的$$$ !Unpin实现,并实现自己的访问器(假定钉扎对于某些特定的!Unpin T是可传递的) PinMut<Vec<T>>实际上并没有为您提供任何安全的方法来获取PinMut<T> ,但是不安全的代码仍然可以利用PinMut::deref来获取原始指针并进行假设指针是稳定的。 因此,我猜这是另一种情况,仅当不安全的代码不依赖!Unpin通过Vec (或其他任何方式)传递时,它才向后兼容。 但是,无论如何,对我来说,对否定推理的严重依赖似乎是可恶的。 如果您想确定自己是!Unpin而不是T则可以随时添加PhantomData<T> (我猜这个参数也适用于原始指针)。 诸如“除非假定类型显式退出或它的文档声明可以依赖此类型,否则不管类型参数如何,在标准代码库中假定类型都是不安全代码都是UB!”这样的笼统声明。

不过,基于宏的代码很有趣。 如果那真的很安全,那将非常巧妙。 我不认为它起初会起作用,因为在一个范围下降期间出现的恐慌可能导致其他范围的泄漏,但是显然这已由MIR解决了吗?

如果panic-during-drop会导致跳过其余局部变量的丢弃,那将是MIR中的一个错误。 那应该只是从“正常下降”切换到“放松下降”。 然后,另一个恐慌将终止程序。

@RalfJung

如果panic-during-drop会导致跳过其余局部变量的丢弃,那将是MIR中的一个错误。 那应该只是从“正常下降”切换到“放松下降”。 然后,另一个恐慌将终止程序。

在这种情况下,是否要删除结构中的其他字段? 从任何面向用户的文档中,这对我来说确实不是很清楚(实际上,这是您所谈论的全部保证的事实-我只是发现它实际上被认为是需要从问题跟踪器修复的错误)。

@comex

关于Pinned (您的建议)的问题是,我认为如果我们想实现它(一旦Rust具有所有功能就位),那么我们不需要做更多的事情向后兼容现有代码:

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

(我认为还建议采用从PinPinnedderef实现)。 看看似乎行不通的地方很有启发性。 例如:

impl Drop for Pinned<TwoGenerators>

PinMut不起作用(至少不是直接)。 即使我们假设在构造Pinned类型时,它会用TwoGenerators本身代替Drop (我不确定这将如何工作?),Rust仍然不会知道要为所投影的任何字段调用构造函数的Pinned版本,因为这些字段将仅按值保存。 如果只使用析构函数的固定版本(如果存在的话),那么它实际上与PinDrop ,只是语法很怪异,我看不出它会更好。

但是,人们倾向于通过递归分析某个值是否植于Pinned来考虑选择一个析构函数。 观察到,如果我们曾经想允许按值特征对象,那么我们就不必依赖于能够决定在编译时使用Pinned<T> drop还是T drop; 我想您是在考虑在这种情况下Pinned版本的vtable条目吗? 这个想法对我来说很有趣。 它肯定需要大量的编译器支持(比建议的PinDrop还要多),但是从某些方面来说可能更好。

然而,重读线程,我记得还有其他问题: deref用于固定类型的实施确实没有很好的工作, Pinned<T> (我怀疑这是因为deref实施PinMut上的逻辑上在某种程度上是错误的,这就是为什么它会不断引起问题的原因,但是鉴于其方便性,很难证明丢失它是合理的-除非您无条件地创建大量类型的Unpin )。 具体来说,我发现RefCell示例非常麻烦,因为在存在Pinned::deref的情况下,这意味着我们甚至无法使用现有类型来动态实施固定(我不知道如果专业化就足够了)。 这进一步表明,如果我们保持deref实施,我们将不得不结束了复制API表面几乎一样多与Pinned ,因为我们做与Pin ; 如果我们不保留它,则Pinned<T>变得难以使用。 除非我们将?Move?DynSized分开添加(如线程中指出),否则Box<Pinned<T>>似乎也不起作用。

所有这些都可能是个好主意,但是在当前Rust的背景下,整件事似乎对我来说并不吸引人,尤其是当您考虑到当前Rust中的任何方法基本上都没有?Move界限(这意味着缺少除非类型为Unpin ,否则deref的确会受到伤害,在这种情况下Pinned没有优势)。 我怀疑它通过固定拥有的财产来模拟实际发生的事情,这反过来使得更难摆脱诸如PinMut::deref类的临时决策,并使界面更加令人愉悦(无论如何主观) ,但是这样做会增加很多语言机制,而且听起来好像您认为其中的任何一种都不是特别有用(与您提议的本机不可移动类型相比)。 我也不知道我们得到的是不能完全向后兼容(大多数情况下,根据引脚状态调用不同的drop实现)是否真的有用,即使可以做到(也许您可以保存一个如果您知道类型是固定的,有时会在析构函数中分支?)。 因此,我目前不确定是否应该更改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> 。 如果我们改变了路线并为&mut Pinned<T>PinMut<T>的别名,那么我们可以回去并弃用所有这些重复的特征,但这真是愚蠢的。 最好不要首先放置重复项。

  2. 另一方面,新设计的或特定于生成器的特征可能会以PinMut<Self>作为唯一选择,但代价是为想要实现特征但又不固定且不需要的类型增加了噪音被固定。 (具体来说,假设Self: Unpin ,呼叫者必须呼叫PinMut::new&mut selfPinMut<Self> )即使Pin<T>变成了&mut Pinned<T>别名,将无法消除这种噪音。 我设想的将来的本机不可移动类型将与可移动类型处于同一情况,当它们始终被视为固定时,不必将它们包装在Pinned

我将在第二篇文章中回复您的其余文章。

关于Drop

我对您所说的Drop有点困惑,但是就您试图使其与PinMut向后兼容的程度而言,我不会考虑因为我认为这不是一个好方法。

我认为最好的方法是,如果您具有TwoGenerators ,则有两种选择:

  1. 无需手动Drop IMPL为任一TwoGeneratorsPinned<TwoGenerators> ;
  2. 您将Drop隐含为Pinned<TwoGenerators> ; 同时,为您提供访问器的宏将为TwoGenerators本身生成一个Drop impl,将其自身强制转换为&mut Pinned<TwoGenerators>并将其删除。 (这很安全:将&mut T&mut Pinned<T>的不变性是,在借用结束后,对于Drop ,您将不会移动T Drop ,您将拥有将为此值创建的最后一个借阅。)

拥有两个选择的唯一原因是,如前所述,您可能不希望您的结构隐含Drop因为借借检查器对不隐含结构的处理更为宽松。

我不明白为什么您要为固定状态和非固定状态分别设置一个析构函数,所以无需使用vtable来区分它们。

关于RefCell

我不认为Pinned::deref应该存在。 安全的宏生成的字段访问器应该足够; 我看不出它“多么难以使用”。 它比能够使用本机字段语法稍微好一点,但是总有一天它将由本机不可移动结构修复。 无论如何,如果很难使用,同样的问题也适用于Pin

这样可以避免RefCell

特别是当您考虑到当前Rust中的任何方法基本上都没有?Move界限(这意味着缺少deref确实会伤害[..])

相反,所有绑定?Sized对象隐式?Move

这是有道理的,因为通常, ?Sized代码不可能假设可移动性。 唯一的例外是不安全的代码,该代码先调用size_of_val ,然后调用memcpy的那么多字节,这就是为什么我们需要这样的hack: size_of_val会为不可移动的类型感到恐慌(不赞成使用新功能有适当的界限)。

我对您所说的Drop有点困惑,但是就您试图使其与PinMut向后兼容的程度而言,我不会考虑这一点,因为我认为这不是一个好方法。

我说的是,如果某些内容PinMut向后兼容,则应该有充分的理由。 但是,您要提出的内容在每个特定方面都与PinDrop在功能上相同,除了您想在Pinned<T>上实现它(在当前的Rust中不起作用)。 就个人而言,我认为专门研究Drop开创了一个非常可疑的先例,并且由于与固定无关的原因,几乎可以肯定它是不受欢迎的,因此我不认为这有任何内在的优势。 无论如何,我认为PinDrop可以与您提案的其余部分分开。

无论如何,如果很难使用,同样的问题也适用于Pin。

当然,如果我们愿意摆脱PinMut::deref ,它也可以与RefCell类的类型很好地组合在一起; 区别在于,我们仍然可以在支持deref同时将PinMut的解决方案拼凑在一起,这似乎不适用于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类型提供半体面的支持。

相反,所有具有?Sized范围的内容都隐式地是?Move。

是的,这很不错。 不幸的是,一大堆Rust代码不适用于绑定了!Sized ,因为它不是默认值,但至少有一部分可以。 我不认为这是一个引人注目的优势,因为我可以对无大小值执行的大多数操作都对它们调用&&mut方法(例如,切片或特征对象),而这两种方法都不因为您不希望Pinned::deref ,所以我可以根据您的建议进行操作( Unpin类型除外)。 也许可以通过使#[derive]实现也为Pinned<T>生成不同的实例来处理常见情况?

Drop

就个人而言,我认为专门研究Drop设置了一个可疑的先例,并且由于与固定无关的原因,几乎可以肯定它是不受欢迎的,因此我不认为这有任何内在的优势。 无论如何,我认为PinDrop可以与提案的其余部分分开。

我同意这是可分离的,但我不认为这是可疑的。 至少……您说的没错,这是一种专业化形式。 它不是从字面上专门化Drop父类隐含实现,而是由编译器生成的放置胶水通过仅在实现时调用Drop等效于特定化。 “ userland”实现看起来像这样(忽略了您不能手动调用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 impls的原因与专业化本身目前不健全的原因相同: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> -是保证的一个子集什么的专业化会允许,因此,如果我们今天对此进行特殊处理,它将最终消失为更一般的规则。

但是同样,这是可分离的。 如果人们不喜欢这个,我们可以有一个单独的特征。

Unpin

但是我真的不确定我同意没有deref实际上只是一个小问题:例如,这意味着在当前状态下,您无法对&Pinned<Vec<T>>做任何事情,其中T: !Unpin ,基本上所有现有的库类型都一样。 这是由于Unpin工作方式引起的问题,而不是您拥有的特定类型的引用。

嗯...好吧,让我纠正我的说法。 Pinned::deref应该存在,但应该限制在Unpin –尽管我将其称为Move

唯一的原因, Deref IMPL为PinMut会导致问题RefCell是,(不像DerefMut IMPL)这不是界Unpin 。 而没有界限的原因是希望允许用户获得&MyImmovableType ,允许不固定的类型使用采用&self方法来隐含特征,并传递给采用&T通用函数。 &mut self基本上是不可能的,但是它主要与&self因为您无法通过mem::swapmem::replace移出RefCell 。 但是,推理是这样的,即使对不可变引用的限制感觉是任意的,即使导致不合时宜,与现有引用的兼容性也足够有价值,应该得到支持。

使用Pinned ,我们可以同时支持不可变和可变引用:您只需将特征加在Pinned<MyStruct>而不是直接加在MyStruct 。 缺点是,它与采用&T但分别具有Self: Sized约束的特征或函数不兼容; 但是这些相对较少,而且经常是无意的。

有趣的是, 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的绑定的一部分(更正:并非如此,但是在尝试根据结果尝试存储任意类型之前,不安全的代码应进行检查)的size_of_val ); 将来会使用大小不正确的右值,它将是ptr::readmem::swapmem::replace的界限(从Sized放松); 如果我们得到&move ,那一定是让你离开其中的界限; 并且类似的内容适用于保证复制省略。

因此,只要我们具有此特质,就没有理由不让Pinned::deref (和deref_mut )的约束为T: Move

[编辑:正如pythonesque提醒我的那样,它与Unpin具有不同的语义,所以请不要介意。]

(然后,诸如VecBox将要手动隐含Move以便无论元素类型是否为Move

嗯...好吧,让我纠正我的说法。 Pinned :: deref应该存在,但是应该在Unpin上受限制–尽管我将其称为Move。

好吧,等一下这是?MoveMove吗? 前者意味着在许多情况下甚至无法构造!Unpin类型。 后者让我感到奇怪,我们究竟是如何指称Pinned<T>类的类型的(因为?DynSized并非真正适合它们)。 我当然希望它们不是同一个人-否则,再次将Move当作Unpin来做我们要避免的确切事情,并使类型在构造时不动。

缺点是,它与采用&T的特征或函数不兼容,但分别具有Self:Sized约束; 但是这些相对较少,而且经常是无意的。

在实际操作方面还有一个更重要的缺点,那就是这些特质或功能很少与&Pinned一起使用今天。 可以使它们与之一起使用,但是这需要大量额外的特征实现,并且(如我所说)可能需要对现有的#[derive]实现进行大修。 这也是必须为新事物支付的成本-如果您希望它可以在!Unpin类型上工作,则还必须以&Pinned<Self>来实现所有内容。 对于采用&mut self ,与使用PinMut &mut self相比,这种情况要好得多,但对于&self ,情况更糟(我怀疑)是更普遍的情况。 因此,为什么我说我认为这是更正确的解决方案(从某种意义上说,如果我们现有的Rust库不多,则Pinned版本会更好),但可能不是更有用的解决方案。

正如我已经说过的那样,使用Pinned时,特征会继续采用&self或&mut self,而您会暗示将其用于Pinned

这次只是用Pinned重新实现Vec的API曲面中的每个特征,对我来说听起来并不那么棒(特别是因为某些特征甚至无法使用它)。 我很确定要么根据情况选择性地实现Deref (例如,让&Pinned<Vec<T>>转到&[Pinned<T>] ),要么只是让整个VecUnpin (并且不允许引脚突出),方法更加理智。 无论如何,两者都是比绝对不做的工作更多,并且必须在一大堆现有的类型和特征上进行复制,以使不可移动的内容与它们一起工作。

我可以说服我-我实际上更喜欢Pinned解决方案-但我只是不知道Pinned<T>上所有这些新特性的实现实际上在哪里将会来自对我来说,人们似乎更不会愿意去实施它们。

有趣的是,“固定”本身根本不需要“固定”存在。 毕竟,为什么有人会真正创建&Pinned>?

这样做的理由很充分(例如:您的固定类型中有Vec )。 侵入式收集将非常频繁地遇到这种情况。 我认为,任何以人们永远不会想要Pinned引用现有容器为前提,或者您必须选择使Unpin起作用的想法的提议都不太可能奏效。 不能通过添加Unpin来基本上选择加入Rust的常规生态系统,这将带来难以置信的破坏性(事实上,几乎我为不动类型使用的每个用例都会变得更加困难)。

借助PinMut,各种特性都将成为PinMut,因此即使是针对可移动类型的这些特性的隐含内容也必须接收PinMut。

当然! Pinned版本的最大优点是,对于可变的固定引用,您不需要独特的特征。 但是,在几乎所有其他情况下,它都比带有deref PinMut更糟或中立。

同时拥有固定和非固定访问器都是不合理的,但是单独使用它应该没问题。

手动访问器需要不安全的代码来实现声音,这对我来说是个坏主意; 我看不出有任何这样的建议可以使访问器生成变得安全(如何阻止某人提供固定访问器而不让他们写unsafe来断言他们不会这样做?)。 但是,正如您注意到的那样,使用Move (假设它实际上意味着Unpin )可以正常工作。

因此,只要我们具有此特征,就没有理由不将Pinned :: deref(和deref_mut)与T:Move绑定在一起。

当然。 我在这里专门谈论!Unpin类型。 Unpin类型对于PinMut也不存在任何构成问题,因此从我的角度来看它们并不相关。 但是,泛型的Unpin (或Move )界限不是令人满意的,理想情况下,您应该能够尽可能地避免使用它们。 再次,就像我之前说的那样: Unpin以及!Sized所隐含的内容都不相同,我不认为您可以将它们视为相同。

…但是,是的,我们确实需要具有Move特性,只是不适合Pinned。 例如,它将是新版本的size_of_val的绑定的一部分; 将来会使用大小不一的右值,它将成为ptr :: read和mem :: swap和mem :: replace的界限(从Sized放松); 如果我们得到了&move,那将是让您摆脱困境的必然之举; 并且类似的内容适用于保证复制省略。

我认为这又是将!Unpin (表示类型可以具有非默认的固定不变)和!DynSized -像!Move混合在一起; 如果不引起不必要的冻结行为,它们就不可能真正相同。

糟糕,您是完全正确的。 Unpin不能与Move

因此,我想我要相信Unpin ,因此Pinned::deref根本不应该存在,相反,我们应该避免在任何情况下(例如使用访问器生成宏)获得类似&Pinned<MovableType> 。 但是也许有人认为它应该作为单独的特征存在。

仅这次用Pinned重新实现Vec的API表面中的每个特征,对我来说听起来都不是那么棒(特别是因为某些特征甚至无法使用它)。 我很确定要么根据情况选择性地实现Deref (例如,让&Pinned<Vec<T>>转到&[Pinned<T>] ),要么只是让整个VecUnpin (并且不允许引脚投影),方法更加理智。

是的,我绝对不是要重新实现整个Vec的API表面或类似的东西。

我同意,最好不要在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版本的最大优点是,对于可变的固定引用,您不需要独特的特征。 然而,更糟糕的是或高于中性PinMutderef ,几乎每一个其他情形。

它还具有避免与RefCell发生冲突的优点,诚然以与其他事物发生冲突( Sized范围)为代价。

手动访问器需要不安全的代码来实现声音,这对我来说是个坏主意; 我看不出任何这样的建议如何使访问器生成变得安全(如何阻止某人提供非固定的访问器,而又不使他们写不安全的说法来断言他们不会这样做?)。

因为访问器直接包含在Pinned<MyStruct> ,而不是直接包含在MyStruct 。 如果您有&mut MyStruct ,则始终可以手动访问字段以获得&mut MyField ,但是您仍处于可移动状态。 如果您有&mut Pinned<MyStruct> ,则无法获得&mut MyStruct (假设MyStruct!UnpinUnpin不存在),因此,您必须使用访问器才能访问这些字段。 访问器需要&mut Pinned<MyStruct> (即,它需要&mut self并包含在Pinned<MyStruct> ),并为您提供&mut Pinned<MyField>&mut MyField ,取决于您选择的选项。 但是您只能使用一种访问器或另一种类型的访问器,因为关键不变性是您一定不能获得&mut Pinned<MyField> ,对其进行写入,释放借用然后获得&mut MyField (并移动它)。

这样做的理由非常充分(例如:您的固定类型中包含Vec )。 侵入式收集将非常频繁地遇到这种情况。 我认为,任何以人们永远不希望Pinned引用现有容器为前提,或者您必须选择使Unpin起作用的想法的提议都不太可能奏效。 通过添加Unpin绑定基本上不能加入Rust的常规生态系统将造成难以置信的破坏性(事实上,几乎我为不动类型使用的每个用例都会变得更加困难)。

我不完全理解您的意思,但这可能是因为您对UnpinMove的错误做出了反应:)

现在,我已更正...如果Unpin存在,则Vec应该隐含它。 但是假设它不存在,那么您所指的场景到底是什么?

对于具有Vec作为其字段之一的结构,我在上面说明了如何获得对该字段的非固定引用(以无法获得对该字段的固定引用为代价)很好)。

我想如果您想要一个通用结构,该结构的字段可能包含Vec ,或者可能包含不可移动的类型(取决于type参数),那将是一个问题。 但是,可能有另一种方法可以解决此问题而无需Unpin特征,所有特性都必须考虑是否实现。

@comex

或者,如何允许从Vec进行转换前往Vec>,哪个将具有大多数Vec API,但忽略了可能导致重新分配的方法?

因为...

也就是说,从当前结构Vec更改Vec的定义构造Vec,对于一些不太傻的名称,基本上Sized成为了ActuallySized + Move的别名; 然后将Sized绑定添加到可能导致重新分配的方法,以及进行转换的方法。

...这听起来确实非常复杂,实际上并没有完成您想要的事情(从&mut Pinned<Vec<T>>获得常规的Vec<T> &mut Pinned<Vec<T>>或其他)。 这挺酷的,它可以让您固定一个矢量的事实后,所以你得到一个不错的模拟到Box<Pinned<T>> ,但这是一个正交关注; 这只是一个事实,即固定为拥有类型的属性可能是正确的。 我认为有关Unpin内容几乎都与引用类型的构造问题无关。

现在,我已更正...如果存在Unpin,则Vec应该执行它。 但是假设它不存在,那么您所指的场景到底是什么?

我将对此做出回应,因为我认为这可以说明我的观点:如果没有Unpin我什至无法通过PinMut改变i32字段。 在某些时候,如果您想对结构进行任何操作,通常都必须在其中移动某些内容(除非它是完全不变的)。 需要人们在Pinned<MyType>上显式实现字段访问器似乎很烦人,特别是如果有问题的字段始终可以安全移动时。 这种方法似乎也与内置的Pinned类型使用时真的很混乱,因为合法的预测会以某种方式随字段而变化,而不依赖于字段类型,Rust已经拒绝了这种方法当mut字段被删除时(和IMO,如果我们要添加这样的注释, unsafe是一个更好的选择,因为实际上不安全的字段是一个强大的步枪)。 由于内置固定类型只是使固定枚举易于使用的唯一方法,因此,我确实关心它们能否与语言的其余部分保持一致。

但更重要的是...

我想如果您想要一个通用结构,而该结构的字段可能包含一个Vec,或者可能包含一个不固定的类型,这取决于类型参数,那将是一个问题

这几乎是Unpin的杀手级用例,而且(对我而言)它实际上似乎有效的事实非常棒,并且可以验证整个固定模型(以至于我认为即使我们从在Rust 1.0之前,我们可能希望保持Unpin不变)。 如果当前的计划(使几乎所有安全的类似于引用的类型无条件地实现Unpin ),则构造函数中内联的泛型参数几乎也是唯一一次需要绑定Unpin情况。经过。

但是我真正没有得到的是:为什么要删除Unpin ? 消除它基本上不会给您带来任何好处。 从PinnedPinMut (反之亦然)中获得的所有美好事物几乎与它的存在无关。 添加它可以使您轻松与Rust生态系统的其余部分兼容。 FWIW, UnpinPinderef的无条件实现也几乎彼此无关,除了没有Unpin所有类型的感觉都相同!Unpin类型所带来的痛苦(这意味着它可能会使无条件的deref实现更加有用)。 我忍不住想念我一些东西。

在这种情况下,是否要删除结构中的其他字段? 从任何面向用户的文档中我真的不清楚

公平地说,我打开了https://github.com/rust-lang/rust/issues/50765进行跟踪。


@pythonesque

具体来说,我发现RefCell示例非常麻烦,因为在存在Pinned :: deref的情况下,它甚至意味着我们甚至无法使用现有类型动态地强制进行固定(我不知道专门化是否足够)。 这进一步表明,如果我们保留deref的实现,我们最终将不得不不得不像使用Pin一样,用Pinned复制API表面。 如果我们不保留,固定变得难以使用。

RefCell的解决方案是提供额外的方法borrow_pinborrow_pin_mut (采用Pin<RefCell<T>> ),并跟踪RefCell内部的固定状态PinMutPinned 。 因此,您在这里认为Pinned没有帮助吗? 对于RefCell也不应使事情变得更糟。

但是你写

不同之处在于我们仍然可以在支持deref的同时使用PinMut将解决方案拼凑在一起,这似乎不适用于Pinned

而且我不知道您指的是什么,为什么这不适用于Pinned

@comex

我认为Pinned :: deref不应该存在。 安全的宏生成的字段访问器应该足够; 我看不出它“多么难以使用”。

我看不到deref和字段访问器之间的连接。 但我也看不出如何deref变得更成问题Pinned<T> ,所以我想我会等待一个答案,我的上述问题的第一位。

就像@pythonesque一样,我认为跟踪引用后面的类型(“拥有的类型”)上的固定状态从根本上更正确。 但是,我怀疑它是否真的可以变成更符合人体工程学的整体API,特别是考虑到与现有Rust生态系统一起工作的约束。

如果我们要故意采用我们认为不太“基本正确”的方法,那么在稳定它之前,我们至少应留出大量时间进行试验,这样我们就可以尽可能合理地确信自己不会最后要后悔了。

@pythonesque ,哇,非常感谢您的广泛总结! 很高兴得知正在制定RFC。 :)

只要调用不可变的引用固定值的函数,只要该函数不使用mem::swapmem::replace 。 因此,让这些函数使用Unpin绑定比使PinUnpin值的每个可变的deref不安全更为自然。

如果稍后将某个函数更新为使用swap ,则在对固定值的可变引用上调用该函数将不再安全。 当swapreplace具有此界限时,更新的函数也必须这样做,这很明显这不是向后兼容的更改。

所以我有一些想法:

  1. 从根本上说, Drop提供与Unpin相同的特权-您可以获得&mut到以前在PinMutDrop是安全的,这意味着Unpin应该是安全的(这是mem::forget并再次泄漏)。
  2. 很好,因为这意味着诸如当前基于期货的API之类的不处理unpin生成器的东西都可以100%安全地实现,即使它们花费self: PinMut<Self> (没有unsafe impl Unpin )也是如此。
  3. 如果Unpin安全,API会发出声音吗? 答案是肯定的:只要生成器不实现Unpin ,并且将项目固定为!Unpin类型都是不安全的,那么一切都是安全的。
  4. 但这意味着销钉突出是不安全的! 不理想。
  5. 如果Self不实现UnpinDrop [编辑:正确或错误?],销钉投影是安全的吗?我们可以自动执行该检查吗?

我有一些想法的更多语言的支持替代这个库的API,其中包括删除Unpin完全,而是翻转极性-一个Pin特质,你选择加入得到这些保证,而不是选择退出。 但这需要大量的语言支持,而当前的实现完全基于库。 我想多了以后再发表。


另一个说明,因为我一直忘记:

引脚投影的安全性取决于Self类型,而不取决于字段类型,因为字段类型必须保证其公共API的安全,这是无条件的。 因此,这不是递归检查-如果Self从未将任何内容移出Pin ,则将其引脚投影到任何类型的字段都是安全的。

@withoutboats FWIW与我和@cramertj在上一轮讨论中得出的结论完全一致。 而且我相信我们可以首先通过衍生工具发出的一些特定属性自动执行互斥检查。

@withoutboats

从根本上讲,Drop提供与Unpin相同的特权-您可以将&mut更改为PinMut中以前的内容。 并且Drop是安全的,这意味着Unpin应该是安全的(这是mem :: forget和泄漏启示再次出现)。

我看不到与泄漏启示录的联系,但不同意。 我有点犹豫的唯一原因是,只要它只是Drop ,这对我来说就更像是一个角落案例,不需要太多人关心。 不确定这是否是优势。 而且,无论如何,安全的Unpin不仅在这里提高了一致性,而且不再通过使其成为特殊情况来“解决” Drop问题(相反,我们可以认为每个impl Drop带有隐式impl Unpin ); 从您所说的内容来看,它还使得在Future方面更容易使用。 因此,这似乎是一个整体胜利。

@pythonesque除非我丢失了某些东西,否则安全的Unpin也不会对侵入性集合造成任何新问题,不是吗? 如果他们在安全Drop情况下仍能工作,则它们仍应工作。


@withoutboats

如果Self不实现Unpin或Drop [编辑:true或false?]我们可以自动执行该检查吗?

您最初在这里还提到了字段类型,我想问一下为什么您认为这是相关的。 现在,我看到您编辑帖子。 :)我同意这仅与Self类型有关。 在下文中,我用我的话重复你的论点。

本质上,问题是: Self选择其固定不变性? 默认情况下,我们假设(即使存在不安全的代码!)固定不变式与所拥有的不变式完全相同,即,该不变式与位置无关,并且此类型不进行固定。 只要我不能对PinMut<T>做任何事情,只能将它变成&mut T ,那是一个安全的假设。

为了启用字段投影,固定固定应改为“我的所有字段固定在其各自类型的固定不变”。 该不变式很容易证明固定投影的合理性,而与字段类型无关(即,他们可以选择所需的任何固定不变式)。 当然,此不变量与将PinMut<T>转换为&mut T不兼容,因此我们最好确保此类类型不是DropUnpin

我看不到与泄漏启示录的联系,但不同意。

只是一个比喻-Unpin是Drop,而mem :: forget是Rc周期。 mem :: forget最初被标记为不安全,但是没有理由。 (和Rc循环是边缘情况的相同论点是反对标记mem :: forget safe。)

从Discord(从精神上)复制粘贴,我真的很想看到证据,我们还没有解决这个问题:通过使结构化的pin访问器不安全地实现,使Unpin安全地实现(对于任何其他情况,也是如此。引入的不安全特征,对吗?您仍然必须编写不安全代码)。 这让我很烦,因为在大多数情况下,它们是完全安全的-基本上,只要没有明确的Unpin隐式类型,就像我们始终安全,如果没有Drop隐式类型一样。 在当前计划中,我们需要更强大的功能-类型应该有明确的!Unpin隐含含义-对于更少的类型,这将是正确的(这几乎是我们可以在库中完成的所有工作)。

不幸的是,我不知道编译器如何或是否可以检查特定类型的手动Unpin impl,而不是“ has any impl”,而且我不确定它是否与专业化有不良的交互。 如果我们有某种确定的方法来执行该检查,以使类型的创建者不必编写任何不安全的代码即可进行结构固定,那么我认为impl Unpin是安全的,我会更加高兴...这似乎可行吗?

我有一个简单的问题,我现在想了解。 在泛型中,除非您对所有泛型参数使用API​​,否则unpin会像大小一样隐式绑定吗?

为了vec必须正确继续保持安全。

将取消固定为像大小一样的隐式绑定

没有。

必须正确无误才能使vec继续保持安全。

你为什么这么认为?

@MajorBreakfast今天打的烦人的事情: PinMut::get_mut在原始PinMut的生存期内为您提供了&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) })
}

即使FUnpin绑定,我也必须使用一个不安全的块,并且它是完全安全的。 当前,尚无安全的方法来生成保留生存期PinMut的引用。

是的,这只是API中的遗漏。

您要准备PR还是应该?

我能做到。 我们要称它为get_mutget_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 PinArc (和PinRc等)取决于Pin (非Mut版本)。 我愿意添加它,但是我不确定在提供什么保证方面是否达成共识。

我不太确定添加PinArc是否添加了某些内容^^' Arc已经不允许“移出借用的内容”

@MajorBreakfast您可以使用Arc::try_unwrap移出Arc Arc::try_unwrap 。 删除该限制或引入T: Unpin绑定将是向后不兼容的更改。

嗨!
我一直在思考如何使密码访问器安全工作。 据我了解,当您组合使用T: !Unpin + Drop时,pin访问器会出现_only_问题。 为此,我看到了一些讨论,试图防止存在这样的T类型的可能性-例如,使特质!UnpinDrop以各种方式互斥,在不破坏向后兼容性的情况下,这不是一个明确的方法。 这个问题更仔细地观察,我们可以得到安全销存取不会阻止一种被!UnpinDrop ,它只是,这样的类型不能放入Pin<T> 。 我认为阻止该事件的发生是更可行的,而且本着Unpin特性如何工作的精神。

目前,我有一个“解决方案”,以防止从类型安全地进入Pin<T>!UnpinDrop 。 它需要一些我们还没有生锈的功能,这是一个问题,但是我希望它是一个开始。 反正这是代码...

/// 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 Unpin类型以及!Drop ,销访问器都没有问题(据我所知)。 这不会破坏任何使用Drop现有代码,它只是限制了可以放入Pin结构中的内容。 在有人需要一种不太可能的*案件既!UnpinDrop (并能够真正放在里面Pin ),它们将能够unsafe impl Pinnable为其类型。

*我没有Pin ,但我希望大多数情况下某人需要impl Drop的类型是!Unpin并不是由需要删除的东西本来是!Unpin ,而是由具有Drop字段和!Unpin字段的结构引起的。 在这种情况下,可以将Drop字段分为不包含!Unpin字段的自己的结构。 如果需要的话,我可以进一步详细说明,因为我觉得这种解释有些含糊,但这并不是要成为评论的主体,因此我在此不做赘述。

据我了解,仅当您使用T:!Unpin + Drop组合键时,才会出现pin存取器的问题。

它更像是“或”而不是“与”。

Pin访问器暗含“固定”的“结构性”解释:固定T ,其所有字段也将固定。 另一方面, impl UnpinDrop是安全的,因为它们假定类型根本不关心固定- T固定与否无关紧要; 特别是在这两种情况下,都不会固定T的字段。

但是,当您说!Unpin + !Drop类型是可以安全添加访问器的类型时,您就正确了。 (该类型仍然必须小心,不要在其不安全的代码中做任何错误,但是Unpin ad Drop是打破固定访问者的两种安全方法。)

仔细研究这个问题,我们可以获得安全的Pin访问器而不会阻止类型!取消固定和放下,只是不能将这种类型放入Pin

我不懂您为什么认为足够? 如果是的话,如果无法固定类型,我们为什么还要对固定访问器感兴趣?

它更像是“或”而不是“与”。

现在让我们来讨论一下,因为我的整个想法都基于与此相反的内容,所以如果我误解了这一点,那么其余的想法将无济于事。

就我(当前)的理解而言,类型为Unpin pin访问器是完全正确的,而不管是否掉线,因为Pin<T>实际上是&mut T 。 同样,即使对于!Unpin类型,类型为!Drop pin访问器也是完全正确的,因为drop是唯一可以获得&mut self函数。在!Unpin类型上输入Pin ,因此将无法将任何内容移出。

如果我在此方面犯了一个错误,请告诉我。 但是,如果不是这样,则Unpin + Drop类型或!Unpin + !Drop类型完全可以使用pin访问器,唯一的问题类型是!Unpin + Drop

@ashfordneil

此外,即使是!Unpin类型,!Drop类型的pin访问器也是完全声音的,因为drop是唯一可以在输入Pin时对!Unpin类型进行&mut self的功能,所以没有什么能够搬东西。

如果我写struct Foo(InnerNotUnpin); impl Unpin for Foo {}那么从PinMut<Foo>转到PinMut<InnerNotUnpin>是不明智的。 为了使投影听起来不错,您必须(a)禁止放置水滴,并且(b)仅在字段为Unpin时,确保结构为Unpin Unpin

(a)应该只禁止对!Unpin事物进行暗示吗?
(b)应该通过以下事实来处理:实现Unpin是不安全的-确保可以通过不安全地声明某个类型在实际上不能固定时可以取消固定而将自己踢到脚上-但它应该尽可能地贴上标签Sync东西,当它不应该出现时,会以这种不合理的行为结束

(a)应该只禁止对!Unpin正确的事物施加隐含暗示吗?

是的,但是由于向后兼容的原因,这是不可能的。

这就是我要说的-禁止掉落暗示什么!出于向后兼容的原因,无法取消固定。 但是,我们不需要防止在!Unpin上发生掉落提示。 类型上的!Unpin标记没有任何意义,并且在该类型位于Pin内之前不作任何保证,因此,只要类型不在Pin_内,!Unpin类型的drop impl完全是_。 我的建议是,将Pin从Pin<T>更改Pin<T: Pinnable> ,其中Pinnable特性可作为网守来防止Drop + !Unpin (或更多类型)通常,将其放置在Pin

那时,您正在使Unpin特性完全无用,不是吗? 而且,您仍然无法使用当前的Rust表达必要的界限。 如果我们可以说“ UnpinDrop不兼容”,那不会有问题。 您似乎需要与Pinnable类似的东西。 我看不出这有什么帮助。

Unpin特征本身已经是“无用的”。 特征_only_表示一旦固定了类型,该类型便可以在进入Pin之前移动。

@RalfJung和我刚刚开会讨论这些API,我们同意我们准备在不进行重大API更改的情况下

解决悬而未决的问题

我对pin API的信念是:

  1. PinMutUnpin的核心API概念构成了不涉及内置语言支持的最佳解决方案。
  2. 如果我们决定必要的话,这些API并不排除将来的内置语言支持。

关于它们的局限性和权衡,还有一些悬而未决的问题,我想记录一下我们的决定。

Drop

直到合并RFC之后,我们才发现的一个问题是Drop特性的问题。 Drop::drop方法具有一个自带&mut self的API。 这实质上是“取消固定” Self ,违反了固定类型应该提供的保证。

幸运的是,这不会影响我们尝试用引脚进行投影的核心“固定生成器”类型的健全性:它们没有实现析构函数,因此它们的!Unpin实现实际上意味着它

但是,这意味着对于您自己定义的类型,“取消固定”它们是安全的,您要做的就是实现Drop 。 这意味着Unpinunsafe trait并没有给我们带来任何额外的安全性:就健全性而言,实现Drop与实现Unpin一样好。关心。

因此,我们已将Unpin特性作为实现的安全特性。

销钉投影

当前API的主要限制是,当该字段未实现Unpin时,无法自动确定执行引脚投影是安全的。 引脚投影为:

给定一个类型T有现场aU ,我可以放心地“项目”从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不必_prove_这是安全的。 即,当前实现这种投影方法需要不安全的代码。 幸运的是,您可以证明它是安全的。 如果字段类型可能未实现Unpin ,则只有在所有这些都成立的情况下这才是安全的:

  1. Self类型不实现Unpin ,或者仅在字段类型实现时有条件地实现Unpin
  2. Self类型不会实现Drop ,否则Self类型的析构函数永远不会将任何内容移出该字段。

使用语言扩展,我们可以让编译器检查其中一些条件是否成立(例如, Self不实现Drop而仅有条件地实现Unpin )。

由于Drop这个稳定的特征,这个问题已经存在我们可以对此问题做出的任何决定,而且如果不进行语言更改就无法自动使图钉投影安全。 有一天,我们可能会进行这些更改,但是我们不会因此而阻止这些API的稳定。

指定“ pin”保证

在RFC流程的后期, @ cramertj意识到,除了对不固定生成器所需的扩展之外,可以使用固定功能来安全地封装对入侵列表的保证。 拉尔夫(Ralf)在这里写了这个扩展的描述。

本质上,固定的保证是:

如果您有PinMut<T> ,并且T没有实现Unpin ,则不会移动T并且不会备份支持T的内存直到析构函数以T运行之后才失效

这与“泄漏自由”并不完全相同- T仍然可以泄漏,例如被卡在Rc循环后面,但是即使泄漏,也意味着内存将永远不会失效(直到程序终止),因为析构函数将永远不会运行。

这种保证排除了某些用于堆栈固定的API,因此我们一开始不确定是否要扩展固定以包括此功能。 但是,出于以下几个原因,最终我们应该采用这种保证:

  1. 还发现了其他更符合人体工程学的堆栈固定API,例如此宏,从而消除了缺点。
  2. 上行空间很大! 这些侵入式列表可能会对像josephine这样的库产生很大的影响,该库将Rust与垃圾收集的语言集成在一起。
  3. Ralf从未确定其中某些API确实是正确的(特别是基于“永久借用”的API)。

API重组

但是,我确实想对API进行最后的提议更改,即四处移动。 回顾现有的API,Ralf&I意识到没有一个合适的地方来描述与Drop等有关的高级保证和不变式。 因此,我建议

  1. 我们创建一个新的std::pin模块。 模块文档将提供固定的高级概述,提供的保证以及不安全部分的不变用户。
  2. 我们将PinMutPinBoxstd::memstd::boxed移到pin模块中。 我们将Unpin保留在std::marker模块中,并保留其他标记特征。

在稳定这些类型之前,我们还需要实际编写更详尽的文档。

添加Unpin实现

Ralf和我还讨论了在标准库中添加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 。 但是,我个人认为,应该可以将钉扎视为已钉扎到位的内存块,因此通过指针间接定向进行的所有投影都应该是不安全的。

我们可以在不影响稳定的情况下延迟此决定,并随时间增加这些影响。

结论

到目前为止,我真的很想听听其他人对pin API的投入,以及这种稳定计划听起来是否合理。 对于后续的语言团队,我将在随后的文章中用fcp提案进行简短的总结。

@rfcbot fcp合并

我提议在进行一些小规模重组后,稳定现有的pin API。 您可以在我以前的文章中阅读更长的摘要。 我认为TL; DR是当前的API:

  • 声音
  • 无需更改语言即可获得的最佳效果
  • 向前兼容可使其变得更好的语言更改

在过去的几个月中,我们已经解决了有关固定的确切保证和不变性的所有主要问题,我们目前的决定(我认为这是我们应该坚持的决定)已记录在较长的文章中。

在实际稳定之前,我建议进行一些更改:

  1. 在新的std::pin模块下重组PinMutPinBox ,该模块将包含有关不变量和一般固定的高级文档。
  2. 为标准类型中的类型添加Unpin无条件实现,这些类型在指针间接后方或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是安全的)已在我的摘要帖子中介绍。 从长远来看,人们应该通过阅读std::pin (稳定性的阻止者)中的文档(而不是挖掘原始RFC)来了解钉扎API。

如果目的是文档阻碍稳定性,但文档尚未编写,那么此FCP是否还为时过早? IMO希望潜在的评论者通过拼凑不同的来源来自己重建暂定文档,这是IMO的入门门槛比人们希望的更高。

@bstrie是在文档之前提出FCP的标准程序。 我对比赛的状态写了一个非常广泛的总结性评论,它应该为没有遵循跟踪问题的任何人提供足够的信息,为那些不想那么了解上下文的人提供简短的评论。

为了进一步详细说明,FCP是“我们是否希望稳定这一问题?”的问题。 如果答案是“否”,那么浪费了许多关于文档的工作。 FCP完成并不意味着该功能立即变得稳定。 这意味着已经做出了稳定它的决定,因此现在是进行为此工作所需时间的时候了; 包括编译器和文档工作。

2018年8月2日,下午12:56,小船[email protected]写道:

@bstrie是在文档之前提出FCP的标准程序。 我对比赛的状态写了一个非常广泛的总结性评论,应该为未遵循跟踪问题的任何人提供足够的信息

-
您收到此邮件是因为您发表了评论。
直接回复此电子邮件,在GitHub上查看,或使该线程静音。

@withoutboats

绝对不能将通过指针的针脚投影视为安全(请参见下面的注释框)。
由于内部易变而引起的销钉突出不应被视为安全的。

您能否澄清一下这一点? 您是否专门在标准库中引用类型? 与Box类似,类似Mutex类型可以投影固定,因为它们唯一地拥有其内容(虽然可能是带外的)。 至少在标准库之外,我假设我们希望具有并发原语,该并发原语在调用.lock()时将PinRef<InPlaceMux<T>>投影到PinMut<T> .lock() ,这似乎例如“通过内部可变性进行投影”的情况。

@cramertj投影到Mutex似乎很危险; 可以先先安全转换为&Mutex ,然后再转换为Mutex::lock以获得&mut ,然后再移走。

编辑:哦,你的意思是一个总是固定的Mutex 。 像PinMutex 。 是的,那行得通。 如果您从不分发&mut则内部可变性很好。 但是目前看来不太可能在libstd中拥有这样的数据结构。

但是,是的,为了精确起见始终固定(即,永不发出&mut ,通过内部可变性进行固定投影才是安全的”)。

@RalfJung好的,是的。

但是目前看来不太可能在libstd中拥有这样的数据结构。

是的,我想确保关于不通过指针或内部可变性进行投射的规定要求专门针对libstd,而不是一般规则。

@cramertj绝对不是

我对稳定这一决定感到失望。 我不太担心短期内会发生什么,因为如果没有编译器支持,任何潜在的设计都会显得有些骇人听闻。 但是,从长远来看,如果Rust获得本机的不可变类型,除非将它们与接受&self&mut self特征方法一起使用,否则它们不会真正地成为一流的。 因此,固定必须是引用类型的属性,而不是引用类型的属性。 当然,在将来针对本机不动类型的设计中,仍然可能会发生这种情况。 但这将导致Pin<T>变得多余–不仅是某些本机&pin T的已弃用别名,而且完全不必要。 反过来,接受Pin<Self>特质,例如Future (以及可能的许多其他特质)则需要进行修改或弃用,这需要一个混乱的过渡期。

凌乱,不是没有可能。 但是冒着过于悲观的风险,我担心避免这种过渡的愿望会使将来的决策偏向于采用&pin来代替本机不动产的类型–我认为这将离开他们永远是二等的。 而且我仍然认为,在短期设计中, &Pinned<T>方法是可行的,避免了这些前向兼容性问题,并且还具有其他好处(例如,完全避免了内部可变性问题)。

那好吧。 照原样,我仍然期待使用Pin ,但是我宁愿将其保留在crates.io而不是进入标准库。

我同意,现在解决这个问题为时尚早。 当前,在某些情况下,例如在访问PinMut<Self>的方法中访问!Unpin类型的字段时,它有些尴尬。 我认为@withoutboats的更改会改善这种情况,但我不确定。 等到应用这些更改并至少测试一段时间后,是否值得?

@Thomasdezeeuw您认为哪些更改可以改善这种情况我建议的更改似乎与这种不便无关。

无法编写通用解决方案-即向std添加方法-但是宏与Future的这种变化是安全的:

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这个问题在我的总结的“ Pin投影”部分中进行了讨论; 除非您还保证Self不会做某些其他事情(我们无法生成检查),否则投影到!Unpin字段是不安全的。 我们只能自动保证能够投影到实现Unpin字段上(因为这始终是安全的,我们可以使用where子句进行检查)。

@withoutboats我已经阅读了摘要,但是也许我误会了。 但是,如果类型T!UnpinPinMut<T>将不会实现DerefMut for T ,因此,如果没有不安全的代码,则无法移动该值。 您仍然会遇到Drop特性的问题,但这要大得多,然后才可以访问类型的字段。 因此,即使T.field!Unpin我也看不出是什么导致从PinMut<T>PinMut<T.field>映射不安全。

@Thomasdezeeuw在Futures-rs中,有些情况是我们在PinMut内有一个类型,但其中一个字段不被认为是固定的

@withoutboats宏的self的可变引用。 结构字段访问没有此限制。

我认为决定稳定钉扎API是正确的决定。 但是,我建议先实施建议的core::pin模块。

您仍然存在Drop特质的问题,但这要大得多,然后才可以访问类型的字段。

不,不是。 Drop实际上很好,只要您不执行字段映射。 这就是字段映射不安全的原因。

您仍然存在Drop特质的问题,但这要大得多,然后才可以访问类型的字段。 所以我看不到是什么使PinMut映射到PinMut不安全的,即使T.field是!Unpin。

需要明确的是,我们也不能自动生成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所做的一切都很好。

@withoutboats宏的

您可以编写允许访问多个字段的方法,但是您必须为您关心的每种组合都这样做。

出于好奇,除了期货之外,还有其他具体的用例吗?或者至少有其他某种动机激发了现在稳定这些用例的欲望(与后来的更多经验相对)?

关于@comex提出的观点,这里似乎没有进一步的讨论。 他们让我感到非常好奇,因为我不记得让pin成为该类型的属性而不是引用的想法。 这已经在别处讨论过了吗? 从“外部”跟踪所有内容并不容易,我尽力了;-)

反过来,诸如Future(以及可能的其他许多特征)等接受Pin的特征将需要改版或弃用,这将需要一个混乱的过渡期。

嗯某些与版本有关的魔术可以让我们默默地包装和拆开这些Pin s,并使更改完全向后兼容吗? 总的来说,但不是太过分,这样可以在过渡期间保持语言和API的整洁。

也许需要更多地充实这个主意来回答这个问题(我记得这个问题是在线程中提出来的,但是我不知道在哪里。)

@yasammez我能够在这里讨论关于价值导向的讨论

只是为了确认,稳定的道路不包括安全的野外预测计划? 在我看来,这将在所有期货库中引入很多不安全的代码。 根据我的经验,未来的手动提示并不罕见,我知道我所有的人都需要对字段进行轮询。

我认为许多不安全的代码将被证明是安全的并不重要。 仅仅存在不安全代码会削弱人们审计更真实的不安全代码的能力。

@tikue在短期内,您可以:

  1. 使用诸如期货库开发的unsafe_pinned宏之类的宏,将每次内部民意测验限制为一个不安全,您必须对照我的简短摘要中所述的约束进行验证(您也不能使用宏和只需手动编写访问器,它也只有一个unsafe )。
  2. 要求您的字段实现Unpin ,使这样的预测变得非常安全并且不需要不安全的代码,但要以堆分配任何!Unpin期货为代价。

@yasammez在此线程中进行了详细讨论: https : //internals.rust-lang.org/t/can-pin-own-its-referent-instead-of-borrowing-mutably/7238/20

@withoutboats我理解这些选项,但是很难理解。 要求Unpin将限制与整个期货的兼容性,例如任何自借的期货,例如使用渠道的任何异步外汇。 不安全的污染是一个真正的问题。

实际上,我已经尝试过将库迁移到Futures 0.3,并且由于这些原因,发现它很费劲。

@tikue这与以自动方式为您提供支票向前兼容。 我们还没有完全设计或实现这种功能,但是这里没有什么阻止它发生。

但是,我并不是很了解这个问题:

不安全的污染是一个真正的问题。

我觉得“不安全问题”常常留在这个水平上,只是对unsafe的全面禁止。

对于大多数嵌套的期货,应该很容易确定是否可以安全地使用unsafe_pinned!宏。 清单通常如下所示:

  1. 我没有为自己的未来手动实现Unpin ,而是仅依靠auto trait impl。
  2. 我没有为将来手动实现Drop ,因为我没有自定义析构函数。
  3. 我要投影到的字段是私有的。

在那种情况下:你很好! unsafe_pinned可以安全使用。

如果您确实手动实现了UnpinDrop ,则必须认真考虑一下,但是即使在那种情况下,它也不一定是一个难题:

  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>> ,其中DelayDeadline的字段。

        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
如果self.open_connections()> 0 => {,则轮询:: Ready(None)
^^^^在模式防护中可变地借用
返回Poll :: Pending;
}
投票:: Ready(None)=> {
返回Poll :: Ready(None);
}
}
``

您对map_unchecked的评论是正确的,也许有一个更通用的签名,它也可以允许围绕指针返回临时对象。

是否存在从PinMut<Type>字段中安全获取&mut引用的准则? 我认为只要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重构

有点启发性的结构,昨晚我弄清楚了如何重构此API,以便只有一个Pin类型可以包装一个指针,而不必创建每个指针的固定版本。 这绝不是对API进行根本性的重塑,但是将“固定内存”组件拉成可组合的部分感觉更好。

当我正在为网络堆栈上NT-RS ,有人告诉我,固定的引用将与“所有权舞”是我目前正在解决帮助fold所看到这里(tx.send移动TX到Send<<type of tx>>未来),因此很难循环传入的数据以发送数据包。) 固定会以哪种方式帮助这类事情?

@Redrield send()在期货0.3中获得&mut自我。

@withoutboats我渴望在Futures-rs中尝试这个新的API!

由于新的API使用不同的标识符,因此我认为应该可以同时使用两个API。 我认为最好在两个API都可用的短暂过渡期内进行试验,准备PR并将libcore中的Futures API移植到新样式。

@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问为什么约束Deref的实现有额外的通用T吗? 您不能只使用P :: Target吗?

这两个标记之间的区别完全没有区别,只是它们的编写方式不同。

为什么都不用Pin

本身也不是Pin

:: new_unchecked限制为P:Deref?

我没有意见; 从历史上看,std库没有绑定到结构上,但是我不确定该策略是出于动机还​​是历史性意外。


计划在#53227着陆后实施此操作,但不会实施Own特征,因为它有争议。 现在,只需在BoxRcArc上使用pinned固有构造函数。

对有些什么@ arielb1写的后续行动,我认为Pin这里实际作用于“指针类型构造” -比如Box&'a mut已“种” * -> * 。 当然,我们实际上没有语法,但这是我可以通过不变量来理解这一点的方式。 我们仍然有4个(安全性(每种类型的不变式,如我的博客文章所述:拥有,共享,固定拥有,固定共享。

我认为适当的形式化需要这种观点,因为想法是Pin<Ptr><T>接受T的不变量,对其进行转换(在各处使用固定的不变量),然后应用Ptr构造函数表示该结果类型。 (这需要更改定义Owned ,但是无论如何我都是长期计划的。)就形式主义而言,人们真的更喜欢写Ptr<Pin<T>> ,但是我们已经尝试过那并且它在Rust中不能很好地工作。

那么,当“选择加入” Pin时,指针类型构造函数的承诺是,应用Deref / DerefMut是安全的:当输入实际上是Ptr应用于该类型的Pinned-Owned / Shared变体,输出将满足该类型的Pinnd-Owned / Shared变体。

这些实际上是这里仅有的两个“不安全方法”(在某种意义上,它们必须注意不变性):

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

我宁愿如果我们可以改变DerefDerefMut为实现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 ,而pin有get_mut_unchecked 。 好像我们应该坚持一致的做法?

有人可以将此添加到rfcbot的关注中吗?

@rfcbot关注get_mut_unchecked_mut_mut

我刚刚意识到我们忘记了rustc会在周围复制内容的一种情况-到目前为止,这可能并不重要。 我说的是打包结构。 如果打包的结构具有需要删除的字段,rustc将发出代码以将该字段的数据复制到对齐的位置,然后在该位置调用drop 。 这是为了确保在&mut传递给drop实际上是对齐的。

对我们来说,这意味着repr(packed)结构不得为“结构”或“递归”。 固定-即使结构本身也不能将其字段视为固定。 特别是,在这种结构上使用pin-accessor-macro是不安全的。 这应该添加到其文档中。

这似乎是一个巨大的步枪,应该贴在所有固定文档上。

@alercah我不认为它比现有的实现UnpinDrop功能更像是步枪-当然,固定值比#[repr(packed)]更常见于固定值

这还算公平。 我担心的是,有人可能会认为投影对于他们未编写的类型是安全的,并且没有意识到packed使其不安全,因为它绝对不是显而易见的。 确实,无论谁进行投影,都有责任意识到这一点,但是我认为,在可能发生这种投影的任何地方,都需要另外记录下来。

嗯,这是否还意味着所有固定结构都可以是Unpin而不管字段类型是什么,因为固定不是递归的?

@alercah我对打包类型的实现不是很熟悉,但是我相信将其固定打包类型是安全的,只是不要通过打包类型来固定。 因此,唯一无法控制打包类型的情况是,您投影到其他人类型的公共字段时,由于Drop或其他原因,这可能同样不安全。 通常,似乎不建议将投影固定在其他人的字段上,除非他们提供表示其安全的投影。

嗯,这是否还意味着所有打包结构都可以取消固定,而与字段类型无关,因为固定不是递归的?

我可以想象一个用例是一个侵入式链表,您只在乎整个结构的地址,但是它有很多要对齐的对齐不良的数据,而无需关心该数据的地址。

由于我与Futures的合作,我不得不学习Pin API,但我有一个问题。

  • 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 ,而不是Box本身。 在@Pauan的原始示例中,他们有一个Pin<&mut Box<...>> ,仅固定了Box 。 请参阅我的游乐场链接,其中显示Pin<Box<...>>防止对框中的东西进行可变引用。

请注意, PinBox最近被删除,并且Pin<Box<T>>现在具有与PinBox相同的语义。

@tmandry PinBox<T>已被删除,并在Nightly上用Pin<Box<T>>代替(您提供的doc链接适用于Stable)。 是正确的每晚链接。

哦,自从我上次使用这些规则以来,规则一定已经更改。 对困惑感到抱歉。

@tmandry是的,更改是最近的。 由于情况仍在变化中,因此很难跟上所有变化。

@tikue的评论是正确的。 您需要记住,引脚仅向下固定一层间接寻址。

@tikue @tmandry @withoutboats感谢您的回答! 这非常有帮助。

那么,这两个问题的现状如何? ( api-refactorget_mut_unchecked_mut_mut )作为急于等待异步/等待系列功能的人,我想知道Pin API会针对哪个rustc版本吗? 有估计吗?

@ crlf0710请参阅稳定建议

@withoutboats好像完成了吗? 我们可以关闭吗?

好的,很抱歉,如果不是将其发布的地方,但是我一直在考虑Drop + !Unpin问题,并提出了以下想法:

  1. 理想情况下,如果Drop::dropfn(self: Pin<&mut Self>)则不会有问题。 我们称这样的Drop PinDrop 。 我们不能只替换DropPinDrop因为retrocompatibility问题。
  1. 由于Drop::drop(&mut self)的唯一问题是在Drop + !Unpin情况下,我们可以为Drop + Unpin导出PinDrop的默认隐含值(因为Pin<&mut T> : DerefMut<Target = T> )并使PinDrop成为rustc自动使用的特征(感谢Pin::new_unchecked(&mut self) ,因为在我们考虑时drop是唯一的堆栈固定情况)。

这是一个大概的想法PoC: https ://play.rust-lang.org/?version = debug&edition =9aae40afe732babeafef9dab3d7525a8

无论如何,恕我直言,这应该保留在beta并且即使它是一个例外,它也不会稳定。 如果有一段时间Drop行为可以依赖Unpin而不会破坏compat,那么现在就是现在。

@danielhenrymantilla我不知道如何解决与现有通用drop impls(如impl<T> Drop for Vec<T>的兼容性问题。

没错,这还需要另外一件事:
隐式T: Unpin绑定到所有泛型,并以与Sized相同的方式使用?Unpin退出

那应该使它成为

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

隐式T: Unpin绑定到所有泛型,并以与Sized相同的方式使用?Unpin退出

这对整个API设计有巨大影响,在?Move提议中进行了广泛讨论。 例如,这意味着许多现有的库需要进行更新才能使用固定。 结论是采用像现在这样的纯库解决方案更好,因为它不需要这些。

是的,短期内成本很高,因为所有现有库都需要更新为!Unpin兼容,但从长远来看,我们最终会获得一个“更安全”的Drop 。 起初似乎并没有那么糟糕,因为至少我们没有破坏任何东西。

但这是一个令人担忧的问题(我不知道它以前曾提出过;感谢您指出, @RalfJung ),而且我认为短期的实际弊端确实会抵消drop(Pin<&mut Self>)的一点安全收益。

是否有关于Pin类型哈希在地址上实现Hash讨论?

Pin应该有Hash的实现,该实现只是委托给所包含的指针,基于地址的哈希没有优先级(而且我不认为固定值应该改变方式的任何原因它被散列)。

此页面是否有帮助?
0 / 5 - 0 等级