@rfcbot fcp合并
功能名称: pin
稳定目标:1.32.0
跟踪问题:#49150
相关RFC:rust-lang / rfcs#2349
这是一个稳定pin
库功能的建议,使“固定”
用于稳定固定内存的API。
(我曾尝试将此提案写为全面的“稳定报告”。)
[std|core]::pin::Pin
这样可以稳定std
/ core
的pin
子模块中的Pin
类型。 Pin
是
基本的,透明的包装,围绕通用类型P
,旨在
成为指针类型(例如, Pin<&mut T>
和Pin<Box<T>>
都是
有效的预期构造)。 Pin
包装器将指针修改为“ pin”
它所指向的内存,防止用户将物体移出
的记忆。
使用Pin
类型的通常方法是构造一些固定的变体
一种拥有指针( Box
, Rc
等)。 拥有所有指针的标准库
提供一个pinned
构造函数,该构造函数将返回此结果。 然后,操纵
值内部,所有这些指针都提供了一种向Pin<&T>
降级的方式
和Pin<&mut T>
。 固定的指针可以取消引用,使您返回&T
,但是不能
安全可变地反引用:只有使用不安全的get_mut
才有可能
功能。
结果,任何需要通过引脚更改数据的人都必须坚持
他们永远不会移出这些数据。 这允许其他代码
安全地假设数据永远不会移动,允许包含
例子)自我参考。
Pin
类型将具有以下稳定的API:
impl<P> Pin<P> where P: Deref, P::Target: Unpin
fn new(pointer: P) -> Pin<P>
impl<P> Pin<P> where P: Deref
unsafe fn new_unchecked(pointer: P) -> Pin<P>
fn as_ref(&self) -> Pin<&P::Target>
impl<P> Pin<P> where P: DerefMut
fn as_mut(&mut self) -> Pin<&mut P::Target>
fn set(&mut self, P::Target);
impl<'a, T: ?Sized> Pin<&'a T>
unsafe fn map_unchecked<U, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
fn get_ref(self) -> &'a T
impl<'a, T: ?Sized> Pin<&'a mut T>
fn into_ref(self) -> Pin<&'a T>
unsafe fn get_unchecked_mut(self) -> &'a mut T
unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin
fn get_mut(self) -> &'a mut T
Pin
大多数特征隐喻死记硬背,这两个对
其操作:
impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { }
std::marker::Unpin
取消固定是一种安全的自动特征,它不选择固定。 如果
固定指针的目标实现Unpin
,可变是安全的
取消引用它。 Unpin
类型不能保证它们不会
从Pin
移出。
这使得按固定名称引用某些内容成为人体工程学
不包含自我引用,因为它会处理非固定的
参考。 Pin
的担保仅对特殊情况类型有效,例如
自引用结构:这些类型不实现Unpin
,因此它们具有
Pin
类型的限制。
标准中Unpin
显着实现:
impl<'a, T: ?Sized> Unpin for &'a T
impl<'a, T: ?Sized> Unpin for &'a mut T
impl<T: ?Sized> Unpin for Box<T>
impl<T: ?Sized> Unpin for Rc<T>
impl<T: ?Sized> Unpin for Arc<T>
这些将固定性不是跨指针传递的概念整理了下来。 那
是, Pin<&T>
仅将T
表示的实际存储块固定在
地点。 用户有时对此感到困惑,并期望该类型
像Pin<&mut Box<T>>
了T
的数据,但只固定了
固定引用实际引用的内存:在这种情况下, Box
的
表示形式,即一个指向堆的指针。
std::marker::Pinned
Pinned
类型是ZST,未实现Unpin
; 它可以让你
禁止稳定执行Unpin
的自动实现,其中!Unpin
表示
还不稳定。
构造函数被添加到std智能指针中以创建固定的引用:
Box::pinned(data: T) -> Pin<Box<T>>
Rc::pinned(data: T) -> Pin<Rc<T>>
Arc::pinned(data: T) -> Pin<Arc<T>>
在过去的9个月中,钉扎API经过了多次迭代,
我们调查了他们的表达能力以及他们的健全性
保证。 我现在可以自信地说,固定API在这里稳定了
健全且足够接近人体工程学上的局部最大值,并且
表现力也就是说,准备稳定。
固定的棘手问题之一是确定何时可以安全执行
销投影:即从Pin<P<Target = Foo>>
转到
Pin<P<Target = Bar>>
,其中Bar
是Foo
的字段。 幸运的是,我们有
能够整理出一套规则,可以帮助用户确定是否
投影很安全:
(Foo: Unpin) implies (Bar: Unpin)
固定项目是安全的:Foo
(包含类型)为Unpin
而Bar
(投影类型)不是Unpin
。Foo
期间从未移动Bar
时,这才是安全的,Foo
没有析构函数,或者析构函数是谨慎的Foo
(包含类型)不是repr(packed)
才是安全的,此外,std API没有提供将对象固定到堆栈的安全方法。
这是因为无法使用功能API安全地实现该功能。
但是,用户可以通过确保他们将对象安全地固定在堆栈上
创建固定参考后,切勿再次移动对象。
crates.io上的pin-utils
板条箱包含宏,以协助这两个堆栈
固定和固定投影。 堆栈固定宏将对象安全地固定到
使用涉及阴影的技巧进行堆栈,而存在用于投影的宏
这是不安全的,但是避免了您必须在其中写入投影样板
您可能会引入其他不正确的不安全代码。
Unpin
,删除pin::Unpin
重新导出通常,除非在std中从多个位置重新导出内容,否则除非
一个是真正定义的超模块(例如,缩短
std::collections::hash_map::HashMap
到std::collections::HashMap
)。 为了这
原因是,从std::pin::Unpin
重新导出std::marker::Unpin
地点。
同时,还包括其他重要的标记特征,例如发送和同步
在序幕中因此,与其从pin
模块中重新导出Unpin
,不如
加上前奏,我们就不必导入std::marker::Unpin
,
放入pin
原因相同。
当前, Pin
的许多关联函数不使用方法语法。
从理论上讲,这是为了避免与难以理解的内部方法发生冲突。 然而,
此规则未得到一致应用,并且根据我们的经验,
只是让事情变得不方便。 固定指针仅实现不可变
deref,不是可变的deref或按值解引用,限制了解引用的能力
无论如何。 而且,这些名称中的许多都是相当独特的(例如map_unchecked
)
并且不太可能发生冲突。
相反,我们更愿意为Pin
方法赋予应有的优先权; 用户谁
需要访问内部方法始终可以使用UFCS,就像它们一样
如果不使用方法语法,则需要访问Pin方法。
get_mut_unchecked
重命名get_unchecked_mut
当前顺序与标准库中的其他用法不一致。
impl<P> Unpin for Pin<P>
我们的取消固定impls的标准理由不能证明此impl:在Pin<P>
和P
之间没有指针方向。 指针本身的推动包括了它的有用性。
该期货隐含价格将需要更改以添加P: Unpin
绑定。
Pin
标记为repr(transparent)
Pin应该是围绕指针内部的透明包装,并具有相同的表示形式。
引脚API对于安全地操作可以
确保不会被移走。 如果该内存中的对象没有
实施Unpin
,其地址将永远不变。 这对于
创建自引用生成器和异步函数。 结果是,
Pin
类型出现在标准库future
API中,并且很快
也会在生成器的API中显示(#55704)。
稳定Pin
类型及其API是稳定稳定的必要先兆
Future
API,这本身就是稳定
async/await
语法并移动整个futures 0.3
异步IO生态系统
到稳定的Rust上。
cc @cramertj @RalfJung
@rfcbot fcp合并
团队成员@withoutboats建议将其合并。 下一步是由其他带标签的团队成员审核:
顾虑:
一旦大多数审阅者批准(并且最多有2个批准悬而未决),这将进入其最终评论期。 如果您发现在此过程中尚未提出的重大问题,请大声说出来!
请参阅本文档,以获取有关带标签的团队成员可以给我哪些命令的信息。
感谢您在这里的详细文章@withoutboats! 从历史上看,我对Pin
的各种保证也感到困惑,并且我目前对在安全保证下使API稳定的问题有些疑问。 为了帮助自己解决这个问题,尽管我认为我会尝试将这些内容写下来。
在尝试开始写下这些内容时,尽管我不断遇到“ Unpin
什么?”的问题。 我对这是什么以及周围的各种保证感到困惑。 您能否再说一遍,对于T
,又不执行Unpin
意味着什么? 另外,如果Unpin
是一个安全的特性,可以天真地实现它似乎可以用来轻松破坏Pin<T>
的不安全保证,但是我肯定会丢失一些
如果我正确无误,则所有非自引用的类型(即:不是生成器)都将取消固定
这不仅是自我参考,还有Pin
也可以支持的稳定内存地址的其他一些用例。 但是,它们相对较少且相差很远。
我如何理解Unpin
可以安全实现的原因是,通过实现它,您可能会违反所编写的其他不安全代码所要求的不变性(至关重要的是,只有您可以编写此不安全代码,任何外部代码都不能依赖于是否您已实现Unpin
)。 无论您是否已实现Unpin
使用Pin
的安全API都无法做,这会导致不健全。 通过选择使用某些Pin
不安全的API,您可以保证仅在安全的情况下实现Unpin
。 上面“固定和安全注意事项”部分的第1点对此进行了说明。
嗯,我还是不太了解Unpin
。 首先,我只是想了解实现或不推动Unpin
意味着什么。
首先,了解哪种类型自动实现Unpin
可能会有所帮助! 上面已经提到,常见的指针类型(Arc / Rc / Box / references)实现Unpin
,但是我认为就是这样吗? 如果这是一个自动特征,是否意味着如果类型MyType
仅包含指针,它会自动实现Unpin
? 还是没有其他类型自动实现Unpin
?
我一直试图总结或说明Unpin
保证书是什么,但我发现这样做真的很困难。 有人可以再次重申实施Unpin
含义以及不实施Unpin
含义吗?
我想我了解Pin<P>
的保证,在这些保证中您不能移出P::Target
的任何内联成员,但这是对的吗?
@alexcrichton感谢您提出的问题,对于不属于该小组的人,我相信固定API可能会使他们感到困惑。
首先,了解哪种类型会自动实现Unpin可能会有所帮助!
取消固定是一种自动特征,例如发送或同步,因此大多数类型会自动实现。 生成器和异步函数的类型为!Unpin
。 可能包含生成器或异步函数体的类型(换句话说,带有类型参数的类型)也不会自动Unpin
除非它们的类型参数是。
指针类型的显式暗示是使Unpin
即使它们的类型参数不是。 希望在本评论结束时可以更清楚地了解其原因。
有人可以再次重申实施Unpin的含义以及不实施Unpin的含义吗?
这是固定API的基本概念。 首先,给定指针类型P
, Pin<P>
行为类似于P
,除非除非P::Target
实现Unpin
,否则可变地取消引用是不安全的Pin<P>
。 其次,必须维护与Pin
相关的两个基本不变式不安全代码:
Pin<P>
获得了&mut P::Target
Pin<P>
,则切勿移动P::Target
。Pin<P>
,则必须确保在析构函数运行之前,永远无法获得指向该指针所指向数据的未固定指针。所有这些的含义是,如果您构建Pin<P>
,则P
指向的值将永远不会再移动,这是我们对自引用结构以及侵入式结构所需要的保证集合。 但是您可以通过为您的类型实现Unpin
而选择退出此保证。
因此,如果为某个类型实现Unpin
,则表示该类型选择了Pin
的任何其他安全保证-可能会可变地取消引用指向它的指针。 这意味着您要说的是该类型不需要固定就可以安全使用。
移动Rc<T>
这样的指针类型不会移动T
因为T
在指针后面。 同样,将指针固定到Rc<T>
(如Pin<Box<Rc<T>>
)实际上并没有固定T
,而只是固定了该特定指针。 这就是为什么任何将其泛型保留在指针后面的东西都可以实现Unpin
即使它们的泛型没有。
另外,如果Unpin是实施的安全特性,则似乎可以用来轻松破坏Pin的不安全保证
,但是我肯定会丢失一些东西
这是固定API中最棘手的部分之一,一开始我们就弄错了。
Unpin的意思是“即使将某些东西放到别针中,也可以安全地获得对其的可变引用。” 今天还有另一个特质可让您获得相同的访问权限: Drop
。 因此我们发现,由于Drop
是安全的,因此Unpin
也必须是安全的。 这会破坏整个系统吗? 不完全的。
要真正实现自引用类型,将需要不安全的代码-实际上,任何人唯一关心的自引用类型都是编译器为您生成的那些类型:生成器和异步函数状态机。 这些人明确表示他们没有实现Unpin
并且没有Drop
实现,所以对于这些类型,一旦拥有Pin<&mut T>
,他们就会知道从来没有真正获得过可变的引用,因为它们是我们知道不会实现Unpin或Drop的匿名类型。
一旦具有包含这些匿名类型之一的结构(例如将来的组合器),问题就会出现。 为了从Pin<&mut Fuse<Fut>>
变为Pin<&mut Fut>
,您必须执行“固定投影”。 这是您遇到麻烦的地方:如果将项目固定到组合器的将来字段,然后为组合器实现Drop,则可以移出应该固定的字段。
因此,销钉突出是不安全的! 为了在不违反固定不变性的情况下进行销钉投影,您必须保证您绝不会做几件事,这在稳定建议中已列出。
因此,tl; dr: Drop
存在,因此Unpin
必须安全。 但这并不能破坏整个事情,仅意味着pin投影为unsafe
,任何想要pin项目的人都必须坚持一组不变式。
生成器和异步函数状态机。 这些明确表示他们没有实现Unpin并且没有Drop实现,所以对于这些类型,一旦知道了Pin <&mut T>,就永远不会真正获得可变引用,因为它们是我们知道不会实现Unpin或Drop的匿名类型。
异步状态机不应该使用Drop
实现吗? 当异步函数完成或被取消时,异步函数“堆栈”(可能等于状态机中的字段)上的事物需要被破坏。 还是会发生其他情况?
我想在这种情况下,重要的是是否存在impl Drop for Foo {…}
项目,该项目将使用&mut Foo
运行代码,例如,可以使用mem::replace
来“泄漏”并移动Foo
。
这与“滴胶”不同,可以通过ptr::drop_in_place
调用它。 给定的Foo
类型的粘连将调用Drop::drop
如果已实现),然后递归调用每个字段的粘连。 但是那些递归调用永远不会涉及&mut Foo
。
同样,尽管生成器(因此是异步状态机)具有自定义的放置粘连,但它只是基于当前状态放置正确的字段集,它承诺在放置期间永远不会移动任何字段。
我使用的术语(尽管我不认为有任何标准):“掉落胶水”是编译器生成的字段的递归遍历,称为它们的析构函数; “拖放实现”是Drop
特性的一个实现,而“析构函数”是drop胶和Drop实现的组合。 Drop胶不会移动任何东西,因此我们只关心Drop的实现。
是否有人在写关于期货的经济学标章? 考虑到这是多么微妙,这似乎是非常必要的。 特别是我认为,示例,尤其是错误的/错误的实现示例以及它们的不完善示例,将很有启发性。
@Gankro当然,您可以为此而拒绝我。
谢谢大家的解释!
我个人是异步Rust和Pin API的超级新手,但是在最近的几天里我一直在玩它(https://github.com/rust-lang-nursery/futures-rs/pull/1315-在哪里我试图在异步代码中利用固定性插入集合)。
在实验过程中,我对这些API有所担忧:
impl core::future::future::Future+futures_core::future::FusedFuture
,未为std::marker::Pinned
实现特征std::marker::Unpin
std::marker::Pinned
Pin
投影),尝试从pin-utils
复制方法,直到找到一种使用2种不安全方法来完成我所需要的解决方案。async
方法和select
情况下实际使用我的未来。 事实证明,我需要将pin_mut!()
存入期货堆栈。 这并不是真正的堆栈,这使其变得混乱。drop()
方法再次采用&mut self
而不是Pin
。Pin
和Unpin
实际含义。 对于一些更特殊的用例,这似乎是实现细节的泄漏。drop()
之前,我们可能会再次看到一个具有完全相同地址的对象。 这给了它在当前范围和'static
之间的某种虚拟生存期。 有两个相关的概念似乎令人困惑,但是仍然完全不同。 我想知道是否可以用'pinned
类的东西来建模。在进行编译时,我经常想到C ++,其中避免了大多数此类问题:通过删除move构造函数和赋值运算符,可以将类型声明为不可移动。 当类型不可移动时,包含该类型的类型也将不可移动。 因此,属性和需求在本机上流经类型层次结构,并由编译器检查,而不是在某些调用(不是全部,例如不是drop()
)内转发该属性。 我认为这很容易理解。 但是也许不适用于Rust,因为Future
s当前必须在某些东西开始轮询之前被移动? 但另一方面,这可以用具有该特征的新定义来解决,或者将移动和轮询阶段分开。
关于Alex Unpin
评论:我正在慢慢理解Unpin
含义,但我同意这很难理解。 也许另一个名字会有所帮助,但是我现在找不到一个简洁的名字。 ThingsInsideDontBreakIfObjectGetsMoved
, DoesntRequireStableAddress
, PinDoesntMatter
等的东西。
我尚未完全理解的是,为什么从Pin<&mut self>
获得&mut self
并非对所有类型都是安全的。 如果Pin只是表示对象本身的地址是稳定的,那么对于大多数典型的Rust类型,这应该成立。 似乎在Pin中集成了另一个问题,那就是它内部的自引用类型不会中断。 对于那些在拥有Pin
之后进行操作的类型总是不安全的。 但是我认为在大多数情况下,这些将是编译器生成的,不安全甚至原始指针都应该没问题。 对于将来需要手动将组合的调用转发到子字段的组合器,显然需要进行不安全的调用才能在轮询之前创建到这些字段的引脚。 但是在那种情况下,我看到的不安全性与其真正重要的地方(引脚是否仍然有效)有关,而不是与根本无关紧要的其他领域有关。
我的另一个想法是,如果施加一些限制,是否有可能得到一个更简单的系统。 例如,如果需要固定的内容只能在异步方法中使用,而不能与将来的组合器一起使用。 也许可以将事情简化为Pin
或Unpin
。 考虑到对于许多具有select / join支持的代码异步/等待方法可能对组合器有利,这可能不是最大的损失。 但是我还没有真正考虑到这是否真的有帮助。
从积极的一面:能够使用“堆栈”借记编写异步/等待代码非常酷,而且非常需要! 能够对其他用例(如侵入性集合)使用固定功能,将有助于提高性能以及嵌入式系统或内核等目标。 因此,我非常期待对此的解决方案。
次要稳定报告:
fn get_unchecked_mut(self) -> &'a mut T
我猜这实际上是unsafe fn
吗?
@ Matthias247如果T不是Unpin,则无法安全地从Pin<P<T>>
获取&mut T,因为那样的话,您可以mem::swap
将T移出Pin,这会破坏固定东西的目的。
我很难向自己解释的一件事是,使Future与其他特性完全不同的原因是,Pin需要成为其API的一部分。 我的意思是,我凭直觉知道这是因为async / await需要固定,但这是否表示Futures与Iterators有所不同?
民意调查可以将&mut self设为仅对Pin<P>
类型或取消固定的类型实施Future吗?
如何在以Pin为参数的方法中访问可变字段。
这实际上使我想知道Pin
上是否缺少一些方法,
impl<'a, T: ?Sized> Pin<&'a T> {
fn map<U: Unpin, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
}
impl<'a, T: ?Sized> Pin<&'a mut T> {
fn map<U: Unpin, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
}
这些可以在pin_utils::unsafe_unpinned!
宏中使用。
我正在尝试找出_why_这个宏声称不安全。 如果我们是!Unpin
并且有一个Unpin
字段,为什么投影到该字段会不安全?
我可以看到的唯一情况是实现自定义!Unpin
类型,将原始指针指向self的Unpin
字段(并依靠它具有稳定的地址/指向相同实例),然后在同一字段中获取&mut
并将其传递给外部函数。 这似乎是出于同样的原因,为什么实现Unpin
是安全的,但是通过获取指向!Unpin
字段的原始指针,您选择不能够调用某些安全的方法。蜜蜂。
为了使前面的情况更安全,可以在Pinned
上构建一个包装器, Unpin
字段实际上是!Unpin
而不是仅添加Pinned
作为整体结构:
pub struct MustPin<T: Unpin>(T, Pinned);
impl<T: Unpin> MustPin<T> {
pub const fn new(t: T) -> Self { ... }
pub fn get(self: Pin<&Self>) -> *const T { ... }
pub fn get_mut(self: Pin<&mut Self>) -> *mut T { ... }
}
这一切似乎都与当前API向后兼容,它可以消除futures-rs
组合器中的一些不安全因素(例如,它可以安全地访问诸如此类的额外字段),但对于当前用例不是必需的。 我们可能可以尝试使用一些类似的API来实现自定义!Unpin
类型(例如侵入式集合),并在以后添加它们。
@ Nemo157这些映射函数不安全,因为我可以在&mut T
上的mem::swap
函数在返回&mut U
之前将其传递给我。 (可变的是,不变的可能不是不安全的)
编辑:另外,pin-utils宏也不同, unsafe_unpinned
不必与目标类型为Unpin
无关,它只是一个“未固定的投影”-对&mut Field
的投影!Unpin
,它也可以安全使用。
我很难向自己解释的一件事是,使Future与其他特性完全不同的原因是,Pin需要成为其API的一部分。 我的意思是,我凭直觉知道这是因为async / await需要固定,但这是否表示Futures与Iterators有所不同?
没有理论上的差异,但有一些实用的差异。 例如, Iterator
是稳定的。 但是结果是,除非我们找出非常聪明的东西,否则您将永远无法在没有双重间接的情况下在自引用生成器上运行for
循环,即使没有它也应该是完全安全的(因为for循环会消耗并且永远不会移动生成器)。
另一个重要的实用差异是Iterator
和Future
之间的代码模式完全不同。 您不能在10分钟内不希望在未来的等待点借钱,这就是为什么在期货0.1上您会看到这些Arc
出现在各处,因此您可以在两个不同的位置使用相同的变量and_then
通话。 但是我们已经走了很远,甚至根本无法表达自引用迭代器,这对用例而言并不那么重要。
这些地图函数是不安全的,因为我可以在
&mut T
上的mem::swap
函数在返回&mut U
之前通过我
啊,该死,忘记了那部分:皱着眉头:
fn as_mut(&mut self) -> Pin<&mut P::Target>
fn into_ref(self)-> Pin <&'a T>`
应该将它们分别称为as_pinned_mut
和into_pinned_ref
,以避免将它们与实际返回引用的方法混淆吗?
在标准中取消固定的重要实现:
我们也有 impl<P> Unpin for Pin<P> {}
。对于我们使用的类型,这没有效果,似乎更安全吗?
没关系,您已将其列入清单。 ;)
我认为我们必须在稳定之前将Drop
保证金整理出来,否则将为时已晚:
使固定对象(
Pin
引用的目标)的存储无效而未对该对象调用drop
是非法的。
这里的“无效”可能意味着“解除分配”,也可能意味着“重新使用”:将x
从Ok(foo)
更改Err(bar)
, foo
的存储将无效。
这样做的结果是,例如,在不先调用drop::<T>
情况下,取消分配Pin<Box<T>>
, Pin<Rc<T>>
或Pin<Arc<T>>
是非法的。
Deref
, DerefMut
对于如何将Deref
特性重新利用以表示“这是一个聪明的指针”,我仍然感到有些不安。 我们还将Deref
用于其他事物,例如用于“继承”。 那可能是一种反模式,但是它仍然很普遍,并且坦率地说,它很有用。 :D
我认为这不是一个健全的问题。
Unpin
无耻的插件:我为此写了两篇博客文章,这些文章对您有多大的帮助(取决于您喜欢阅读(半)正式语法)是否有用。 ;)从那时起,API发生了变化,但基本思想没有改变。
@ Matthias247我认为您遇到的一个问题是,构建涉及固定的抽象当前几乎总是需要不安全的代码。 使用这些抽象很好,但是例如用安全代码定义将来的组合器将行不通。 这样做的原因是“引脚投影”具有一些限制,我们只能安全地检查编译器的更改。 例如,你问
为什么现在我的drop()方法再次采用&mut self而不是Pin。
好吧, drop()
是旧的-自Rust 1.0起就存在-因此我们无法更改它。 我们只想让它接受Pin<&mut Self>
,然后Unpin
类型可以像现在一样获取它们的&mut
,但这是一个非向后兼容的更改。
为了使编写将来的组合器更安全,我们需要安全的引脚投影,为此,我们必须更改编译器,而这不能在库中完成。 我们几乎可以在derive
proc_macro中做到这一点,除了我们需要一种方法来断言“此类型没有Drop
或Unpin
任何实现”。
我认为值得弄清楚如何为针脚投影获得如此安全的API。 但是,这并不一定要阻止稳定性:我们在此处稳定的API应该与安全的引脚投影向前兼容。 ( Unpin
可能必须成为lang项目才能实现上述断言,但这似乎还不错。)
我经常想到C ++,其中避免了大多数此类问题:通过删除move构造函数和赋值运算符,可以将类型声明为不可移动。 当类型不可移动时,包含该类型的类型也将不可移动。
有几种尝试为Rust定义不可移动的类型。 它很快变得非常复杂。
要了解一个重要的事情是,钉扎不提供不可移动的类型! 所有类型都可以在Rust中移动。 如果您有T
,则可以将其移动到任意位置。 代替不可移动的类型,我们利用Rust的能力来定义新的封装API,因为我们定义了不能从中移出的Pin<&mut T>
)中,而不是在指针对象( T
)中。 类型无法说“我永远都不会动”。 然而,有一类说“如果我得到固定,不要再动我”的一种方式。 因此,一个MyFuture
,我自己永远是可移动的,但Pin<&mut MyFuture>
是一个指向的一个实例MyFuture
不能得到任何移动
这是一个微妙的观点,我们可能应该花一些时间来整理此处的文档,以帮助避免这种非常常见的误解。
但是我们已经走了很远,甚至根本无法表达自引用迭代器,这对用例而言并不那么重要。
这是因为到目前为止,所有迭代器都是使用类型和impl Iterator for …
块定义的。 当状态需要在两次迭代之间保持时,别无选择,只能将其存储在迭代器类型的字段中并与&mut self
。
但是,即使这不是在语言中包含生成器的主要动机,最终能够使用生成器yield
语法定义可以与for
一起使用的语法也将是非常不错的选择yield
之间借用,因为对于生成器迭代器而言,其重要性与生成器期货一样重要。
(
Unpin
可能必须成为lang项才能实现上述断言,但这似乎还不错。)
Unpin
和Pin
已经是lang项目,以支持安全的固定生成器。
好的,谢谢大家的解释! 我同意@Gankro的观点,即关于Pin
的nomicon风格的一章,在这里将非常有用。 我怀疑关于为什么存在各种安全方法以及为什么不安全方法如此不安全等问题已有很多发展历史。
为了帮助自己理解这一点,尽管我想再次尝试写下为什么每个函数都是安全的或为什么是unsafe
。 从上面的理解中,我已经掌握了以下所有内容,但是到处都有一些问题。 如果其他人可以帮我解决这个问题,那就太好了! (或帮助指出我的想法是错误的)
fn new(P) -> Pin<P> where P: Deref, P::Target: Unpin
Pin<P>
的安全方法,并且存在Pin<P>
通常意味着P::Target
永远不会在P::Target
实现了Unpin
,其内容为“Pin
不再持有”。因此,这里的安全性是因为没有unsafe fn new_unchecked(P) -> Pin<P> where P: Deref
unsafe
因为P
不会Unpin
,因此Pin<P>
必须坚持以下保证:Pin<P>
后, P::Target
将永远不会再移动。违反此保证的简单方法,如果此功能安全,则看起来
喜欢:
fn foo<T>(mut a: T, b: T) {
Pin::new_unchecked(&mut a); // should mean `a` can never move again
let a2 = mem::replace(&mut a, b);
// the address of `a` changed to `a2`'s stack slot
}
因此,由用户保证Pin<P>
的确意味着
P::Target
构造后再也不会移动了,所以它是unsafe
!
fn as_ref(&Pin<P>) -> Pin<&P::Target> where P: Deref
Pin<P>
我们保证P::Target
永远不会移动。 那是Pin
合约的一部分。 结果,它琐碎地意味着&P::Target
,指向P::Target
另一个“智能指针”将提供相同的&Pin<P>
可以安全地转换为Pin<&P::Target>
。这是从Pin<SmartPointer<T>>
变为Pin<&T>
的通用方法
fn as_mut(&mut Pin<P>) -> Pin<&mut P::Target> where P: DerefMut
as_ref
大致相同。Pin
,因此没有任何事情可以轻松实现问题:“恶意” DerefMut
impl怎么样? 这是一种安全的方法
调用用户提供的DerefMut
&mut P::Target
本地创建
大概也允许它修改它。 这样安全吗?
fn set(&mut Pin<P>, P::Target); where P: DerefMut
Pin<P>
(以及我们对此一无所知的事实Unpin
),这不应该保证P::Target
永不移动吗? 如果我们可以P::Target
重新初始化它,这不安全吗?unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>
unsafe
函数,所以这里的主要问题是“为什么不是...
问题::这里有什么反例? 如果这是安全的,那是什么
显示违反Pin
担保的示例?
fn get_ref(Pin<&'a T>) -> &'a T
Pin<&T>
的保证意味着T
将永远不会移动。 返回&T
T
进行突变,因此在坚持的同时这样做应该是安全的内部可变性是一个“可能的陷阱”,如果T
是
RefCell<MyType>
? 但是,这并不违反
Pin<&T>
因为担保仅适用于整个T
,而不适用于
内部字段MyType
。 虽然内部可变性可以四处移动
内部,它仍然从根本上不能将整个结构移到
&
参考。
fn into_ref(Pin<&'a mut T>) -> Pin<&'a T>
Pin<&mut T>
意味着T
将永远不会移动。 因此,这意味着Pin<&T>
unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T
Pin<&mut T>
意味着T
绝不能移动,因此这是微不足道的unsafe
mem::replace
T
(安全地)移动unsafe
这里“尽管我给了您&mut T
,但您永远都不允许T
“。unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>
unsafe
至少与上面的相同,&mut T
(不需要不安全的关闭)mem::replace
。 这里可能还有其他不安全因素fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin
Unpin
,类型可以说“ Pin<&mut T>
没有保证,&mut T
“的新型包装器。因此,不能保证&mut T
impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
as_ref
和get_ref
,因此impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }
as_mut
和get_mut
,因此impl<T: ?Sized> Unpin for Box<T>
(以及其他与指针相关的实现)
Box<T>
将实现Unpin
特性T
实现了Unpin
。 这里的实现将甚至T
明确未实现Unpin
,则Box<T>
实现Unpin
。问题:从根本上赋予权力的例子是什么?
例如,如果此隐含内容是安全的,则可能需要unsafe
不存在。
@ Matthias247,您无法从Pin上安全地获取&mut T
>如果T不是取消固定,因为您可以通过mem :: swap将T移出固定引脚,这将使固定事物的目的无效。
谢谢! 是的,因为swap
不是不安全的方法,所以这显然是有问题的。 但是,可以通过添加一个固定Unpin
势必swap()
? 由于到目前为止的所有代码都应为Unpin
或仍然是不安全的,因此这不会破坏任何内容。
我想最让我困惑的一件事是Pin<T>
编码了多个保证:T的地址是稳定的,以及关于它内部状态的一些保证(在某些情况下它是冻结的/不可变的)情况,但也并非如此)。
在我看来,仅将不安全的代码/投影移动到需要进一步基于Pin
的调用的位置(例如,在字段中的poll
s中)可能比处理那些不安全的代码/投影更可取。所有情况。 但是,我现在也意识到了另一个问题:可以访问可变引用的代码可以自由移动该字段,然后在该字段上安全调用drop()
,它可能会中断地址存储在其他地方。 为了解决这个问题,将需要讨论drop(Pin<T>)
超载。
@RalfJung感谢您的解释! 我同意这样的事实,即我通常会尝试做一些不安全的事情,因此,要求我多加理解应该很好。 我更担心的是那些想编写更一般安全的未来组合器的人,但现在可能还会遇到所有这些术语。 如果他们可以编写组合器而根本不了解Unpin
和引脚投影,然后只获得以简化方式工作的组合器(仅适用于Unpin
期货),那似乎是可取的。 因为我还没有尝试过,所以我不能说目前是否如此。 我认为仍然需要手动添加至少Unpin
范围。
我也了解不可移动类型与引脚类型不同的事实。 但是,我目前更关注用例,而不是区别。 对于侵入性集合的用例,不可移动类型可以很好地工作,而不会引入太多复杂性。 对于期货来说,显然需要进行一些研究,因为缺少从可移动类型到不可移动类型的方法。 如果那种方式不比Pin API更符合人体工程学,那也就没有成功。
将T: Unpin
到mem::swap
将意味着即使某些类型不在Pin内,也无法使用它。
但是可以通过添加绑定到swap()的Unpin来解决此问题吗? 由于到目前为止所有代码都应为Unpin或不安全,因此这不会破坏任何内容。
那会破坏所有通用代码:如果您以今天的稳定状态为某个T
没有限制地编写函数,则可以在其上调用swap
。 这必须继续工作。
不可移动类型在不引入过多复杂性的情况下可以很好地工作。
没有人演示过以向后兼容的方式向Rust添加不可移动类型的大大超过为稳定而提议的复杂性。 您应该在RFC存储库中找到其中的一些旧建议和讨论。 有关一个示例,请参见https://github.com/rust-lang/rfcs/pull/1858 。
https://github.com/rust-lang/rfcs/pull/2349上的RFC和从此处开始的Boat博客系列应有助于给您一些背景知识和对其他设计的印象。 (还要注意日期,该设计已经进行了将近10个月!)
另外mem :: swap是一个红色鲱鱼,因为它根本不是一个有趣的功能。 字面上就是
let temp = *a;
*a = *b;
*b = temp;
@Gankro不使用不安全的代码吗? Afaik不可能直接写出来。
编辑:我想另一种思考方式是将T: Unpin
到mem::swap
实际上是在语言级别更改安全性的定义。 它将彻底破坏所有mycrate::swap
fns。
将T:取消固定到mem :: swap意味着即使某些类型不在Pin内,也不能在某些类型上使用。
如果取消固定是自动派生的(与Sync
/ Send
方式相同),那么我认为这应该不是问题。
这将破坏所有通用代码:如果您以当今稳定的状态为某个T编写了一个无约束的函数,则可以对其调用swap。 这必须继续工作。
但这显然是。 没有考虑过特质界限需要在Rust中明确传播这一事实。
没有人演示过以向后兼容的方式向Rust添加不可移动类型的方法,而其复杂性不会大大超过为稳定而提议的复杂性。 您应该在RFC存储库中找到其中的一些旧建议和讨论。 有关示例,请参见rust-lang / rfcs#1858。
谢谢,如果有空的话,我将进一步阅读以前的工作。 显然,已经有很多想法和努力了,我当然不想阻止任何事情。 我只是想在尝试进行此操作时提供我的疑虑和问题。
@ Matthias247
如果取消固定是自动派生的(与同步/发送的方式相同),那么我认为这应该不是问题。
我不确定我是否清楚。 需要说明的是,在!Unpin
类型周围移动是绝对安全的,因此在mem::swap
附近移动是绝对安全的。 将类型T: !Unpin
固定后,即在Pin<P<T>>
内移动是不安全的。
例如,在异步/等待代码中,您可以随意移动从异步函数返回的期货。 一旦将它们放入pin_mut!
或放入Pin<Box<..>>>
或其他货币后,您就只能停止移动它们。
我对此有一些疑问:
考虑到Pin<T>
对于async
和生成器的重要性,以及与Rust的其他部分(例如swap
和replace
)进行交互时潜在的不健全性,是否已对此处提出的固定API的变体进行了正式验证(例如,通过@jhjourdan或@RalfJung)?
这个API是否为Rust的抽象机器/操作语义提供任何新的保证? 即,如果我们忘记了用例作为async
和await
/生成器的支持机制,是否可以将其放在生态系统的板条箱中,并且只要有当前的保证,它就可以工作给?
稳定钉扎API会导致什么样的API或类型系统添加成为不可能? (此问题是2的扩展。)
就其发展而言,待稳定的API提供了什么途径,一种语言提供了&pin T
类型来改进字段投影等(这对于拟议的API来说似乎不太好)。
我有一些笔记:
标准库中的文档似乎很少。 :(
我同意其他人的观点,即固定结构在精神上非常繁琐。
文档中的Unmovable
示例似乎过于复杂,涉及unsafe
; 这似乎不是最优的。 通过在RFC草案中用该语言逐步初始化(即,对NLL进行改进)可以代替:
struct Unmovable<'a> {
data: String,
slice: &'a str,
}
let um: Unmovable<'_>;
um.data = "hello".to_string();
um.slice = &um.data; // OK! we borrow self-referentially.
drop(um); // ERROR! `um.slice` is borrowing `um.data` so you cannot move `um`.
// You won't be able to take a &mut reference to `um` so no `swap` problems.
这涉及零不安全,用户很容易处理。
此外,std API没有提供将对象固定到堆栈的安全方法。
这是因为无法使用功能API安全地实现该功能。
这样的API呢?
pub fn using_pin<T, R, F>(value: T, f: F) -> R
where F: for<'a> FnOnce(Pin<&'a mut T>) -> R {
pin_mut!(value); // Actual implementation inlines this but the point is this API is safe as long as pin_mut! is safe.
f(value)
}
我没有一直密切关注钉扎API的开发,因此本来可以在其他地方提到或解释这个问题,但我一直找不到它,很抱歉,如果是这样的话:
Pinned
类型是ZST,它没有实现Unpin
; 它可以让你
禁止稳定执行Unpin
的自动实现,其中!Unpin
表示
还不稳定。
关于为什么无法使!Unpin
impls保持稳定,有任何解释吗?
仅当Foo(包含类型)不是repr(包装)时才是安全的,
因为这会导致字段移动以重新对齐它们。
打包是否意味着可以动态移动字段? 有点吓人。 我们确定llvm在其他情况下永远不会生成代码来移动字段吗? 同样,是否有可能通过llvm移动堆栈上的固定值?
尽管有一些细微之处,但这似乎是一个非常不错的API。 干得好!
@Centril Ralf在他的博客上写过关于钉扎的
引脚API完全不涉及语言更改,并且使用预先存在的语言功能完全在标准库中实现。 它不会对Rust语言造成影响,也不会排斥任何其他语言功能。
Pin
实际上只是Rust的最有价值和最经典的功能之一的聪明实现:通过标记API unsafe
一部分将不变式引入API的能力。 Pin
包装了一个使一个操作( DerefMut
)不安全的指针,要求执行此操作的人员必须支持某些不变式(不要移出引用),并允许其他代码假设这将永远不会发生。 类似的更老的例子是String
,这使得将非UTF8字节放入字符串中是不安全的,从而允许其他代码假定String
中的所有数据都是UTF8。
关于为什么!Unpin impls不能保持稳定的任何地方都有解释吗?
负隐式符号当前是不稳定的,与这些API完全无关。
负脉冲目前不稳定。
That♂️这很有意义,应该多考虑一下。 谢谢。
@alexcrichton这是对该API的一些出色分析,我们应该尝试将其保存在某个地方,而不是一些会丢失的评论!
一些评论:
as_mut
:问题:“恶意” DerefMut隐式实现如何? 这是一种安全的方法
调用用户提供的DerefMut,该DerefMut原生创建&mut P :: Target,
大概也允许它修改它。 这样安全吗?
基本上,当您调用new_unchecked
,您会对这种类型的Deref
和DerefMut
实现做出承诺。
set
:给定Pin(以及我们对此一无所知的事实
取消固定),这是否不能保证P :: Target永不移动? 如果我们可以
用另一个P :: Target重新初始化它,这不安全吗?
还是这与析构函数之类的东西有关?
这将删除指针的旧内容,并在其中放置新内容。 “固定”并不意味着“从不掉落”,而是“从不掉落直到移动”。 因此在调用drop
时删除它或覆盖它就可以了。 调用drop
至关重要,这就是我上面提到的下降保证。
您在这里看到什么移动?
map_unchecked
:问题:这里的反例是什么? 如果这是安全的,那么显示违反Pin保证的示例是什么?
一个示例将从Pin<&&T>
然后使用此方法获得Pin<&T>
。 固定不会“传播”引用。
get_ref
:这里的一个“也许是陷阱”是内部可变性,如果T是
RefCell?
确实,这是一个陷阱,但是正如您所观察到的那样,这并不是一个健全的问题。 什么是不健全是具有从进入的方法Pin<RefCell<T>>
至Pin<&[mut] T>
。 基本上,发生的是RefCell
不传播固定,我们可以impl<T> Unpin for RefCell<T>
。
是否对此处提出的固定API的变体进行了正式验证(例如,通过@jhjourdan或@RalfJung)?
不,不是我们这边的。 我上面提到的博客文章包含有关如何开始对此进行形式化的一些想法,但实际上我们还没有完成。 如果您给我时间机器或感兴趣的博士生,我们会做的。 ;)
如果我们忘记了用例作为异步和等待/生成器的支持机制,是否可以将其放在生态系统的板条箱中,并且只要我们给出当前的保证,它就可以工作吗?
那是意图。
稳定钉扎API会导致什么样的API或类型系统添加成为不可能? (此问题是2的扩展。)
嗯,我不知道该如何自信地回答这个问题。 可能添加的空间太大而且太高,以至于我都不敢对此做任何通用的声明。
就其发展而言,待稳定的API提供了什么途径,可以提供一种提供了&T字型的语言来改进现场投影等(这对于拟议的API来说似乎不太好)。
我不确定&pin T
,它与新的通用Pin<T>
不太匹配。 在处理预测方面,我们需要说些“没有为此类型实现Unpin
和Drop
”的技巧,然后我们可以使用宏安全地执行此操作。 对于其他人体工程学,我们可能希望使用该语言的通用“字段投影”功能,该功能还将涵盖从&Cell<(A, B)>
到&Cell<A>
。
关于为什么!Unpin impls不能保持稳定的任何地方都有解释吗?
AFAIK否定隐含符号有一些长期的限制,如果您向它们添加通用范围,它们通常将无法以您认为的方式工作。 (通用界限有时会被忽略,等等。)
也许Chalk修复了所有这些问题,也许只是其中的一部分,但是无论哪种方式,我们都可能不想在Chalk上阻止它。
打包是否意味着可以动态移动字段? 有点吓人。
给定drop
期望对齐的引用,这是在打包字段上调用drop
的唯一声音方法。
我们确定llvm在其他情况下永远不会生成代码来移动字段吗? 同样,是否有可能通过llvm移动堆栈上的固定值?
LLVM复制字节应该没有问题,因为它不能更改程序行为。 这是关于“移动”数据的高级概念,在Rust中可以观察到(例如,因为指向数据的指针不再指向它)。 LLVM不能仅将数据移到我们可能指向的其他位置。 同样,LLVM不能仅将已获取其地址的值移动到堆栈中。
好的,谢谢@RalfJung ,很有道理! 一些后续问题...
当您说“从不移动,直到掉落”时,这意味着Drop
可能会在&mut self
已从Pin<&mut Self>
的地址移出的地方调用? 析构函数不能依赖内部指针的准确性,对吗?
在查看set
时,我担心的是如何处理恐慌,但在深入研究代码源后,我认为这不是问题。
当您说“从不移动,直到掉落”时,这意味着
Drop
可能会在&mut self
已从Pin<&mut Self>
的地址移出的地方调用? 析构函数不能依赖内部指针的准确性,对吗?
@alexcrichton我的理解是,这意味着直到Drop::drop
返回之后再也不会移动。 否则,某些用例(至少是侵入性集合和堆栈分配的DMA缓冲区)将变得不可能。
析构函数可以依靠的东西从未被移动,如果他们能证明它以前在Pin
。 例如,如果状态机只能通过要求将其固定的API进入状态,则析构函数可以假定该状态机在被删除之前就已被固定。
代码不能假设非本地析构函数不会移动成员,但是显然您可以假设自己的析构函数不会移动事物,因为您是编写它们的人。
当您说“从不移动直到跌落”时,这意味着Drop可能会被称为&mut self从Pin <&mut Self>的地址移到了哪里? 析构函数不能依赖内部指针的准确性,对吗?
我的意思是,除非调用drop
,否则数据将永远不会移动(即不去其他任何地方,包括不被释放)。 是的, drop
可以依靠在固定位置上运行,即使它的类型不能表达这一点。 drop
应该花费Pin<&mut self>
(对于所有类型),但是but,为时已晚。
调用drop
之后,数据只是无意义的字节,您可以对它们进行任何操作:在其中放入新内容,进行释放,执行任何操作。
例如,这允许使用侵入式链表,其中元素的析构函数通过调整相邻指针来将其注销。 我们知道,不调用该析构函数,内存将不会消失。 (该元素仍然可以泄漏,然后它将永远保留在该链接列表中。但是在那种情况下,它将保留为有效内存,因此没有安全问题。)
我一直在仔细阅读Pin
, Unpin
以及所有内容的讨论内容,尽管我现在想知道发生了什么,但是还有很多关于不同类型的含义以及实现者和用户必须遵循的合同的微妙之处。 可悲的是,我在文档中对std::pin
或Pin
和Unpin
讨论相对较少。 特别是,我很乐意从这些评论中看到一些内容:
纳入文档。 特别:
Pin
只能保护“一级深度”。Pin::new_unchecked
限制了对Deref
和DerefMut
。Pin
和Drop
之间的交互。!Unpin
放置在Pin
,将不允许其移动。Pin<Box<T>>: Unpin where T: !Unpin
的理由。 这与上面的“一级深度”限制联系在一起,但是我认为这个带有适当解释的具体示例将对读者有所帮助。我发现@alexcrichton的反例也很有帮助。 让读者知道,除了散文外,其他unsafe
方法可能会出什么问题,我认为这会有所帮助(至少肯定对我有用)。 总的来说,由于该API的精妙之处,我希望看到各种不安全方法的“安全性”部分得到了扩展,并且可能还从std::pin
模块级文档中进行了引用。
我本人还没有使用过该API,所以我相信现在它处于良好状态的技术判断。 但是,我认为名称Pin
, Pinned
和Unpin
对于非常不同的类型/特征和相对复杂的API来说太相似且表达不充分,这使得理解起来更加困难(如对此线程的一些评论所证明)。
它们似乎遵循标记性状的命名约定,因此我不能真正抱怨,但是我想知道是否可以在冗长性和不解释性的名称之间进行权衡:
Pinned
-有点令人困惑,因为它与Pin
的含义相同。 假设它像PhantomData
使用元信息来增强结构,否则元数据将丢失, PinnedData
, PhantomPinned
, PhantomSelfRef
甚至DisableUnpin
怎么样? Unpin
-像https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922一样,我对名称Unpin
感到困惑,因为“取消固定可以用多种歧义的方式来理解。 像IgnorePin
或PinNeutral
?通常我失败了,但是我自己找到了很好的替代名称...
PhantomPin和PinNeutral让我感到特别好听。
只是为了提供一个对立点,我发现Unpin
直观(一旦我理解了)。 Pinned
很难保持笔直,因为我没有在自己的代码中使用它。 将Pinned
更改NotUnpin
怎么办?
我同意寻找更好的名字可能是一个值得尝试的做法,目的是使固定变得更容易谈论。 我提议:
Pin
-> Pinned
:当您获得Pin
,这实际上是一个承诺,即您所获得的内容将被固定,并将被永久固定。 不过,我对此并不感到太强烈,因为您_可能还会谈论被赋予“价值的支柱”。 Pinned<Box<T>>
对我来说读起来更好,尤其是只有Box
是固定的,而不是固定的。Unpin
-> Repin
:对于其他标记性状,我们通常谈论您可以使用具有该标记性状的东西做什么。 这大概就是为什么首先选择Unpin
原因。 但是,我认为我们真正希望读者喜欢的是,可以固定Unpin
东西,然后将其重新固定在其他地方,而无需考虑任何后果。 我也喜欢@ mark-im的PinNeutral
的建议,尽管它有些冗长。Pinned
-> PermanentPin
:我不认为Pinned
是一个好名字,因为包含Pinned
并没有真正固定。只是不是Unpin
。 PhantomPin
有一个类似的问题,就是当Pin
不是您真正想要得到的东西时,它指向Pin
。 NotUnpin
具有双重负数,因此很难推理。 @Kimundi对PhantomSelfRef
的建议非常接近,尽管我仍然认为它有点“复杂”,并且它将属性“一旦固定就无法移动”与一个实例联系在一起(在这种情况下,有自我参考)。 我的建议也可以写成PermanentlyPinned
; 我不知道哪种形式还不错。我认为Pinned
最终应该是NotX
,其中X
就是Unpin
最终被命名的名称。 Pinned
的唯一工作是制作它,因此封闭类型不实现Unpin
。 (编辑:如果我们将Unpin
更改
Repin
对我来说没有意义,因为“此类型可以固定在其他位置”只是“此类型可以移出图钉”的副作用。
@tikue在某种意义上,我有同感,但相反。 我认为Unpin
应该用负数表示“这不受Pin
约束”,而Pinned
应该用正数表述“受到Pin
约束” s/Unpin/TemporaryPin
, s/Pinned/PermanentPin
吗?
编辑:是的,我看到你对点Repin
是的副作用Unpin
。 我想传达一个事实,对于类型为Unpin
的类型, Pin
是“不重要的”,我认为Unpin
不是很好。 因此,以上TemporaryPin
。
@jonhoo我认为我倾向于相反的主要原因是因为Pinned
阻止了特质的实现,因此,对我而言,这就是真正的负面。
编辑:关于:
Unpin -> Escape
Pinned -> NoEscape
有趣的..我试图查看它如何适合文档。 就像是:
通常,当您获得
Pin<P>
,要保证P
的目标在被删除之前不会移动。 例外是P
的目标是Escape
。 标记为Escape
保证即使移动它们也仍然有效(例如,它们不包含内部自引用),因此允许它们“转义”Pin
。Escape
是自动特征,因此完全由Escape
类型组成的所有类型本身也是Escape
。 实施者可以每晚使用impl !Escape for T {}
或从std::phantom
包括NoEscape
标记来选择退出。
尽管与“转义”一词的联系似乎有点微不足道,但这似乎还不错。 另外,写上面的内容还使我意识到Pin<P>
不能真正保证P
的目标不会移动(正是由于Unpin
)。 相反,它保证_无论_ P
的目标是否移动_或_ P
的目标都不会移动。 虽然不知道如何使用它来更好地选择名称...但是它可能应该使它以一种或另一种方式进入文档。
就个人而言,我也不太喜欢Unpin
作为名称,可能是因为它通常被视为impl !Unpin
读为“ not-un-pin”并且需要几个脑循环(我是较旧的模型)得出的结论是:“好的,一旦它第一次被固定,它将永远被固定”,所以我什至无法优化双重否定。
总的来说,人类倾向于在否定而不是肯定的方面思考(没有直接的来源,但是如果有疑问,请查阅Richard Hudson的著作)。
顺便说一句Repin
对我来说真的很好。
Pin
很难解释,因为它并不总是使固定值不可移动。 Pinned
令人困惑,因为它实际上没有固定任何东西。 它只是防止转义Pin
。
Pin<P<T>>
可以解释为固定值:固定值不能移动,除非该值是可以通过大头针逃脱的类型。
一些快速的谷歌搜索似乎表明,在摔跤比赛中,一人被钉住时,从别针中拔出被称为逃逸。
我也喜欢逃逸一词,而不是Unpin
但我会选择EscapePin
。
这里有很多好的想法!
一件事:对我来说,作为非母语使用者, Pin
和Unpin
主要是动词/动作。 尽管Pin
是有意义的,但是由于该对象一次固定到一个内存位置,因此对于Unpin
我看不到相同的内容。 一旦我收到Pin<&mut T>
的引用,T就会始终固定在某种意义上,即它的内存位置是稳定的,无论是否为Unpin
。 不能真正取消固定对象作为动作。 区别在于Unpin
类型不需要在后续交互中坚持固定的要求。 它们不是自引用的,例如,它们的内存地址未发送到另一个对象并存储在该对象中。
我同意@tikue的观点,对Unpin
实际含义进行出色的工作将是一件很不错的事,但是很难量化。 这些类型不单单是可移动的,还不单单是缺乏自我指称? 可能是因为“移动对象时,整个内存空间中的指针都不会失效”。 然后,像StableOnMoveAfterPin
,或者只是StableMove
可能是一个选项,但是听起来也不太好。
Repin
对我来说具有与Unpin
相同的复杂性,这意味着它首先要固定住一件事-以我的理解,这不会发生。
由于特质主要定义了在看到该类型的Pin
之后会发生什么,因此我发现诸如PinNeutral
或PinInvariant
这样的东西还不错。
关于Pin
与Pinned
,我想我更喜欢Pinned
,因为那是指针看到的时间点的状态。
@ Matthias247我不认为如果P: Unpin
可以保证您保证P::Target
的地址稳定? 我可能对此有误吗?
@ Matthias247
一旦我收到Pin <&mut T>的引用,就一定会固定T,因为它的存储位置是稳定的,无论是否取消固定。
您能否阐明您的意思? 给定T: Unpin
您可以编写以下代码:
let pin_t: Pin<&mut T> = ...
let mut other_t: T = ...
mem::replace(Pin::get_mut(pin_t), &mut other_t);
// Now the value originally behind pin_t is in other_t
@jonhoo其实是个好问题。 我的理由是,将期货装箱,然后将在相同的内存地址上调用其poll()
方法。 但这显然仅适用于顶层任务/期货,并且中间层可能在Unpin
时移动期货。 看来您是对的。
Wrt最新的自行车棚:
Unpin
: MoveFromPin
呢? 如果我仍然不遗漏一些微妙之处,那么我认为这直接说明了该特性实际上可以实现的功能:如果类型在Pin
,则仍然可以移动它。
至关重要的是,它与Unpin
说的是一样的东西,但是被构造为一个肯定的断言,所以现在有了!MoveFromPin
而不是双重否定的!Unpin
!MoveFromPin
。 我想我觉得这更容易解释,至少……是,您不能离开图钉的类型。
(在基本概念上还有一些变化的空间: MoveOutOfPin
, MoveFromPinned
, MoveWhenPinned
,依此类推。)
Pinned
:然后可以变成NoMoveFromPin
,其作用是使类型!MoveFromPin
。 我认为这似乎很简单。
Pin
本身:这一个与其他两个没有关联,也没有那么重要,但是我认为这里也可能会有一些细微的改进。
问题是,例如, Pin<&mut T>
并不意味着&mut
被固定,而是意味着T
是(我想我在至少一份最近的评论证据)。 由于Pin
部分充当&mut
的一种修饰符,我认为在某些情况下,最好将其称为Pinning
。
这样做有一些间接的先例:如果我们要修改整数类型的溢出语义以换行而不是出现紧急情况,我们说Wrapping<i32>
而不是Wrap<i32>
。
所有这些都比原始的要长,但是考虑到围绕它们的某些原因多么微妙,这可能是一个值得投资以提高清晰度的方法。
回复: Repin
,我希望这有点像
unsafe trait Repin {
unsafe fn repin(from: *mut Self, to: *mut Self);
}
这可能被用来支持!Unpin
类型的一个内部Vec
样采集,偶尔移动的内容(这是不是现在或曾经将这种特性的建议,只是我从第一印象特质名称)。
也将名称混为一谈:
Pin<P>
-> Pinned<P>
:指针P
指向的值在其生命周期内固定在内存中(直到删除)。Unpin
-> Moveable
:该值不需要固定,可以自由移动。Pinned
(结构)-> Unmoveable
:需求为Pinned
,并且无法移动。我不认为Pin
或Unpin
应该改变,其他选择都增加了冗长性,我认为这很不清楚,甚至会引起误解。 此外,我们已经进行了此对话,并决定使用Pin
和Unpin
,并且此线程中提出的参数都不是新的。
但是,自上次讨论以来已添加了Pinned
,我认为使其成为PhantomPinned
以清除其像PhantomData
类的幻象标记类型是有意义的。
就我个人而言,我也非常不喜欢Unpin这个名称,也许是因为它通常被视为impl!Unpin,其读作为“ not-un-pin”,并且需要几个大脑循环(我是一个较旧的模型)才能得出结论这意味着“好吧,一旦它第一次被固定,它将永远被固定”,所以我什至无法优化消除双重否定。
这与我的经验完全相反,手动实现!Unpin
意味着您正在使用不安全的代码(这是一个非常小众的用例)手动实现自引用结构。 相比之下,任何在指针后面保留潜在未固定结构的东西都具有Unpin
的正暗示。 所有的impls的Unpin
的std
为正极性,例如。
@withoutboats ,您能否提供指向先前有关这些名称的讨论的链接,而此处提出的论点已经得到了争论?
这是一个线程,尽管在RFC线程和跟踪问题上当然也对此进行了讨论https://internals.rust-lang.org/t/naming-pin-anchor-move/6864
(此线程中称为锚和销的类型现在称为Pin<Box<T>>
和Pin<&'a mut T>
)
是否应将Unpin读作Unpinnable的缩写? 无法固定,因为即使它位于Pin内,您也无法使其保持固定状态。 (对我而言,这是否是正确的理解?)
我浏览了一些文档和注释线程,但没有看到专门针对Unpinnable的任何引用。
Unpin不应该缺少任何东西。 我认为对很多用户来说并不明显,但事实是,标准库样式指南是将动词作为特征名称而不是形容词-因此是Send
,而不是Sendable
。 尚未完全完美地应用此功能,但这是规范。 Unpin
与“取消固定”相同,因为可以从您固定它的Pin
取消固定此类型。
像Move
类的名字(不是“可移动的”,记住)不如Unpin
清晰,因为它们暗示着它必须能够完全移动它,而不是将行为与引脚类型。 您可以移动!Unpin
类型,因为可以在Rust中移动任何Sized
值。
正如我所见,建议使用全短语的名称对于std来说是非常简单的。
它可能并不短,但这正是我的阅读方式。 由于特征是动词,如果要使用它来描述类型而不是对类型进行操作,则必须将其手动转换为形容词。 std::iter::Iterator
由可迭代的事物实现, std::io::Seek
由可寻找的事物实现, std::pin::Unpin
由不可固定的事物实现。
@withoutboats其他一些没有动词问题的名称Escape
或EscapePin
吗? 可以理解这个讨论以前已经发生过,但是现在大概对此有更多的关注,所以我不确定这是完全多余的重演...
有一件事我认为是真实的,它不幸的是, Pin
和Unpin
可以解读为一对(有些类型是“针”,有些是“钉住”),当Pin
应该是名词,而不是动词。 Pin
不是特质的事实有望使事情变得清晰。 支持Pinning
的论点是有道理的,但是在这里我们遇到了名称长度问题。 尤其是因为方法接收者将不得不重复self
两次,所以我们最终会遇到很多字符: self: Pinning<&mut Self>
。 不确信Pinning<P>
是一个完整的四个字符,超过Pin<P>
的清晰度。
@tikue “转义”术语比我想的要钉住得多,与转义分析等概念相冲突。
同样,我们已经进行了对话,并决定使用Pin和Unpin,并且在此线程中提出的参数都不是新的。
这给我带来了错误的认识-社区的经验报告是不受欢迎的吗? 我个人没有看到Unpin
的明确问题,直到该线程中的其他一些注释以及Pinned
double negative的并置。
Rust不会执行转义分析,因此我不确定我是否将其视为真正的问题。
:bell:根据上面的评论,
我仍然希望在着陆之前看到https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891中概述的文档改进:)
我已经打开https://github.com/rust-lang/rust/pull/55992来添加上面建议的文档,并将Pinned
重命名PhantomPinned
。
我认为Pinned
(和PhantomPinned
)鼓励将“固定”值概念化为不能移出Pin
值,这意味着Pin
许多值Unpin
)不是“固定”的!
这似乎令人困惑。 我发现将Pin
中的所有值都固定在Pin
时更容易概念化,而以前固定为Pinned
是是否固定为永久值。 Pinned
控制项。 与Pin*
分开的名称可以防止两个不同概念的混淆。
PhantomNotUnpin
:P
就我个人而言,我也非常不喜欢Unpin这个名称,可能是因为它通常被视为impl!Unpin,其显示为“ not-un-pin”,并且需要几个大脑循环
谢谢! 我也被Unpin
困扰了一段时间,机器人无法查明原因。 现在我想我明白:这是双重否定。
这与我的经验完全相反,手动实现!Unpin意味着您正在使用不安全的代码(一个非常特殊的用例)手工实现自引用结构。 相反,任何将潜在的取消固定结构保留在指针后面的东西都具有肯定的Unpin表示。 例如,std中所有Unpin的信号都是正极性的。
它不仅与实现有关,而且与讨论有关。 impl !Sync
是相当罕见的(不是唯一的,因为它很不稳定),但是谈论Sync
和!Sync
类型是很常见的。 同样,在讨论此功能时, !Unpin
出现了很多,至少是我所拥有的。
我也更喜欢肯定表示属性的东西( MoveFromPin
)。 我并不完全相信人体工程学,因为与Pin
人们不必如此频繁地写出这种特征。
Rust不会执行转义分析,因此我不确定我是否将其视为真正的问题。
LLVM确实如此,因此转义分析仍然与Rust相关。
解决该问题的方法是选择使Pin
/ Unpin
的含义相反的单词。 例如,将Unpin
重命名Relocate
。 然后!Unpin
变成!Relocate
。 这对我来说更直观了-我读为“哦,这种类型的对象无法重定位”。 另一个竞争者是Movable
。
我不确定相反的词是什么可以代替Pin
,或者我们是否需要。 但是我当然可以想象文档会说像这样的话:
当且仅当对象可以在内存中重定位时,才可以通过
DerefMut
直接修改固定对象。Relocate
是自动特征-默认情况下添加。 但是,如果有指向存储您的值的内存的直接指针,请通过在类型上添加impl !Relocate
来退出Relocate
。
impl<T: Relocate> DerefMut for Pin<T> { ... }
对我来说,这比Unpin
更直观。
我已经打开#55992以添加上面建议的文档
不过,这仅添加了https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891中建议的内容的一部分。
我喜欢MoveFromPin
建议。 Relocate
也很好,但可能与Pin
关联不够。 可以再次将其理解为不可移动的类型(不是)。 RelocateFromPin
再好。
Escape
ing在Swift中也与闭包相关联,无论它们是在当前调用链的内部还是外部调用。 这听起来有误导性。
只要能使名称更清楚,我就不会看到名称较长的任何问题。
FWIW我也想投一个票,将Unpin
重命名Relocate
或MoveFromPin
(或更冗长,但也许更准确的MayMoveFromPin
)。
我同意!Unpin
或只是Unpin
的双重负值在历史上一直令我感到困惑,并且以正数表示的东西“尽管在Pin
内也可以移动”,我认为将有助于减轻一些混乱!
FWIW我最初认为与Unpin
,但是当我实际使用IMO时,这是有道理的-这并不是双重否定,因为您要查找的操作是Unpin
(可以自由地从Pin
取出和取出某些东西),而不是把东西固定在别针上的能力。 它与MoveFromPin
,只是用词不同。 我希望使用一个不会让人觉得“不是Pin
”之类的名称,但是IMO MoveFromPin
和其他名称过于罗y。 UndoPin
? ( FreePin
对于haskell人群?)
我仍然认为!Unpin
听起来很奇怪-我训练了自己内心的声音,使其更像是“不要钉住它!” 而不是通常的“不实现Unpin
”,但这需要一些努力。
那么!Pluck
呢?
@runiq
而不是通常的“不实施取消固定”
我在较早的评论中提到了这一点,但是我认为这实际上是一个很好的表达方式: Unpin
是将其从Pin<C<_>>
取出的操作。 没有实现Unpin
不提供该功能。
@cramertj我很喜欢UndoPin
@cramertj我确实同意我到目前为止对提议的替代方案不罗the的MoveFromPin
不是Unpin
。 很好的一点是,它不是双重否定的,但是在阅读它时(作为一个还没有大量使用过它的人),它不断使我绊倒,因为它是双重否定的。 我一直试图将“ Un”前缀读为负数。
我很好奇,但是@cramertj或其他人您认为人体工程学上的Unpin
绑定值有很好的把握吗? 超级稀有吗? 超级普通吗? 如果打字很痛苦,那么常见到足以让人痛苦?
对于简短的名字,我个人喜欢Relocate
想法,但是对于较长而冗长但又可以的名字,因为您不会键入它,所以我非常喜欢MoveFromPin
。 我个人认为,无论人机工程学上键入名称都优于Unpin
我很好奇,但是@cramertj或其他人您觉得对人体工程学上的Unpin绑定有多大
根据我的经验,在两种情况下Unpin
实际上出现在用户代码中:
Unpin
界限是因为您实际上需要在轮询时移动未来(例如,某些精选API之类的东西)。Unpin
的无条件实现,因为那些其他类型是否Unpin
无关紧要,因为您永远不会固定项目给他们。第二种情况的一个例子是,如果您有某种通用的缓冲区类型(例如T: AsRef<[u8]>
)。 您不需要固定它即可获取切片,因此您不必关心它是否实现Unpin
,因此您只想说您的类型无条件地实现Unpin
这样您就可以实现Future
忽略固定。
Unpin
很普遍地被视为一个界限- select!
, StreamExt::next
和其他组合器都要求它们操作的类型为Unpin
。
@withoutboats我很好奇你在那里的第二点; 我们是否认为impl Unpin
是人们经常要记住要实施的东西? 类似于今天的图书馆作者如何经常忘记使用#[derive(Debug)]
或impl std::error::Error
作为自定义类型,这使得使用这些图书馆变得更加困难?
取消固定是自动特征。 只有当该类型显式退出时,您才会拥有不实现取消固定的类型。 (或包含选择不取消固定的字段)。
我知道情况就是如此。 我以为那是迄今为止最常见的情况,这就是为什么令我惊讶的是@withoutboats甚至提到了第二点。 它向我表明,这可能比我原先想像的更普遍(尽管可能仅在实现自己的未来时),所以我对这种用例的出现频率感到好奇:)
@alexcrichton我很好奇,但是@cramertj或其他人,您觉得在按人体工程学设计的把握吗? 超级稀有吗? 超级普通吗? 如果打字很痛苦,那么常见到足以让人痛苦?
我写了一篇很长的文章,讲述了将代码移植到Futures 0.3(使用Pin
)的经验。 您不需要阅读它,摘要是:
大多数时候,您根本不需要担心Unpin
,因为Unpin
几乎针对所有类型都是自动实现的。
因此,您唯一需要担心Unpin
是:
您有一个比其他类型通用的类型(例如struct Foo<A>
)。
并且您想要实现该类型的固定API(例如Future
/ Stream
/ Signal
)。
在这种情况下,您需要使用以下命令:
impl<A> Unpin for Foo<A> where A: Unpin {}
或这个:
impl<A> Unpin for Foo<A> {}
impl<A> Future for Foo<A> where A: Unpin { ... }
通常,这是唯一需要Unpin
的情况。 如您所见,这通常意味着每种类型需要使用Unpin
〜2次。
在使用非组合符的情况下,或者在未实现Future
/ Stream
/ Signal
,不需要使用Unpin
。
因此,我想说Unpin
很少出现,并且只有在创建Future
/ Stream
/ Signal
组合器的情况下才会出现。
因此,我强烈支持MoveFromPin
类的名称。 固定功能是大多数人都不需要处理的利基功能,因此我们不应对名称长度进行过度优化。
我认为认知上的好处(避免双重否定)比在少数情况下保存几个字符更为重要。
特别是因为钉扎已经很难理解! 因此,不要让它变得不必要地困难。
@jonhoo我们是否认为
impl Unpin
是人们必须记住经常执行的东西? 类似于今天的图书馆作者如何经常忘记使用#[derive(Debug)]
或impl std::error::Error
作为自定义类型,这使得使用这些图书馆更加困难?
我认为不可能忘记impl Unpin
,因为如果作者忘记了,他们将得到编译器错误,这将首先阻止其包装箱发布。 因此,它根本不像#[derive(Debug)]
。
@withoutboats所谈论的情况仅适用于实现Future
/ Stream
/ Signal
组合器的人员,它不会影响其他任何人(尤其是不会影响其他人)库的下游用户)。
(我认为双重否定是其中的一部分,也许只是比严格必要而多的否定,但是我觉得这不是全部内容(在此进行自我反省)...“取消固定”有点,“隐喻”或“间接”在解释时是有道理的,并且由于“固定”本身已经是一个隐喻,因此不清楚我的大脑为什么会对这个隐喻作进一步的研究,但是,尽管如此,我的大脑还是发现了由于某种原因而“松开”,并且很难牢牢固定。)
我猜你是正确的@cramertj ,这实际上不是通常意义上的双重否定,但是像@alexcrichton和@glaebhoerl一样,我一直被它绊倒。 “联合国人”作为前缀有一个非常否定-Y感觉它(“不安全”,“未实现”等等是如何我通常会遇到前缀),它是否定寄托在这里,如果仅仅作为一个动词。
@withoutboats我很好奇你在那里的第二点; 我们是否认为必须经常记住实施Unpin? 类似于当今的库作者如何经常忘记为其自定义类型添加#[derive(Debug)]或impl std :: error :: Error,这使得使用这些库变得更加困难?
绝对不! 如果用户手动实现在非未来通用的Future
,则他们可能希望能够在将来的实现中更改状态而无需编写不安全的代码-也就是说,对待Pin<&mut Self>
作为&mut self
。 他们将收到错误消息,指示MyFuture<T: AsRef<[u8]>>
未实现Unpin
。 然后,解决此问题的最佳方法是实现Unpin
。 但这唯一的影响是对试图实现Future
的用户的影响,这是他们无法忘记的,因为他们的代码将无法编译。
@withoutboats所讨论的情况仅适用于实现Future / Stream / Signal组合器的人员,它不会影响其他任何人(特别是,它不会影响库的下游用户)。
我特别是在谈论非组合的通用人工期货,即使它们的通用不是Unpin
,它们也应该只包含Unpin
Unpin
。
我制作了一个问题流程图,“我在实现手动功能/流时如何处理固定?”
为了多骑自行车,今天我在午餐时想出了LeavePin
。 它具有与escape
相同的基调,没有隐含的误导性语义。
@vi不仅没有健全的相互作用,而且不可能有健全的相互作用。 引脚API在标准库中严格定义,并且不涉及任何新的语言功能,用户可以在第三方库中轻松定义它们。 如果现有的库代码不存在某种语言功能,则该时间段不健全,因为任何用户今天都可以编写此库代码,并且可以很好地编译。
如果在此库代码存在的情况下语言功能不健全,则这是不合理的时期,因为任何用户今天都可以编写此库代码,并且可以很好地编译。
并不是说它应该与pin
有任何关系...但是我不认为它是如此简单。
如果使用unsafe
的库功能正在使用行为尚未确定的语言构造(例如&packed.field as *const _
或对ABI进行各种假设),则如果附加语言更改会使假设无效这些库中的一部分,然后我认为是不完善的库,而不是语言的更改。 另一方面,如果语言更改使定义的行为不合理,那是语言更改的错误。 因此,面对不安全和语言变化的情况,良好的编译能力不足以保证库的健全性。
+1到MoveFromPin或类似的东西
如果您问“何时取消为自己的类型取消固定?”这个问题,如果您问“何时取消为我的类型取消实现MoveFromPin?”,答案就更加清楚了。
与“我应该在此处添加取消固定为特质吗?”相同 vs“我应该在这里添加MoveFromPin作为特征吗?”
不固定!移动
抱歉,如果在某处提到了此问题,但是我只是略过围绕Pin的大量讨论,涉及实现问题和RFC问题。
会生锈!移动吗? 我当然可以看到这种情况的用例(哎呀,我来找Pin的原因是我在寻找一种方法,以免用无法移动的类型将自己砸死。) 如果答案是肯定的,那将如何与Pin互动? Pin的存在会使添加!Move变得比现在困难吗?
关于Unpin的命名,应该有一个尚未解决的问题。
正如@RalfJung指出的那样,#55992还仅添加了https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891和其他地方要求的少量额外文档。 不知道那是不合并的理由。
为什么现在我的drop()方法再次采用&mut self而不是Pin。
好吧,drop()很旧-自Rust 1.0起就存在-因此我们无法对其进行更改。 我们希望只使用Pin <&mut Self>,然后Unpin类型可以像现在一样获取它们的&mut,但这是一个非向后兼容的更改。
我想知道是否有可能以向后兼容的方式实施此更改。 AIUI,直到我们添加Unpin
(并且人们可以指定!Unpin
),所有类型都实现Unpin
。 因此,我们可以添加一个特征:
trait DropPinned {
fn drop(Pin<&mut> self);
}
然后将所有Unpin
类型的特征都体现出来,直到人们可以选择退出为止。 就像是:
impl<T> PinDrop for T where T:Unpin + Drop {
fn drop(Pin<&mut T> self) {
Drop::drop(self.get_mut());
}
}
然后,让编译器插入对DropPinned::drop
而不是Drop::drop
调用。 本质上,特征DropPinned
成为lang-item而不是Drop
。 在AFAICS中,当且仅当与Unpin
同时引入此机制时,这才向后兼容。
关于Unpin的命名,应该有一个尚未解决的问题。
@tikue没有任何libs团队成员在FCP之前或期间向rfcbot提出过担忧,而且我不认为有关Unpin
任何论点对该线程是新的还是新颖的,因此通常我们的流程会考虑当前命名完成。 显然,如果有人担心,他们应该大声疾呼,但是在FCP之后放置团队复选框的目的是确保每个人都可以按计划稳定API。
@cramertj我有点困惑。 几个人说了。 当我询问有关Unpin
命名的论点在哪里提出并得到解决的参考文献时,我被指向https://internals.rust-lang.org/t/naming-pin-anchor- move / 6864 ,据我所知,也有人抱怨Unpin
的命名,没有真正的反驳。 https://github.com/rust-lang/rfcs/pull/2349中的原始RFC也没有关于为什么建议的Unpin
替代方案较差的理由。 即使在此线程中,似乎真正出现的唯一反论点是“更短”和“在技术上正确”。 您是否可以指向具体讨论,在此讨论并拒绝更易于理解的替代名称(例如MoveFromPin
)?
我在前面的评论中已经解释了为什么我相信在此主题中提出了新颖的观点。 自成立以来,我一直在密切关注pin API的讨论,并且不记得曾经在此线程之前看到过双重否定问题。
@tikue我提出并看到双重否定问题多次提出,并且将Unpin
的精确命名多次提出,并一直得到Unpin
支持而得到解决。 不包括命名Unpin
作为一个尚未解决的问题:我们已经讨论了替代方案,并且此FCP是决定我们准备稳定已做出的决定的过程,其中包括名称Unpin
。
@cramertj您能否提供讨论发生地点的链接? 我没有怀疑您,只是想看看赞成Unpin
的论点,因为我不认为这里给出了这些论点。 如前所述,到目前为止,我所提供的参考文献并未提供有关Unpin
命名的任何解决方法。
@cramertj +1到@jonhoo的询问。 如果有libs团队之间的讨论未在官方渠道中注册,那么我认为在此应该重申这些讨论的主旨。 我认为甚至有一个正式规则,即RFC决策只能基于公开已知的论点做出?
我认为甚至有一个正式规则,即RFC决策只能基于公开已知的论点做出?
是的,从记录上来说,我不在libs团队中,因此也没有参加过任何libs-team讨论。 查看RFC线程和Pin
跟踪问题,发现了很多次出现Unpin
名称的情况。 我没有看到任何人明确说的话“双负”,但我肯定还记得“ !Unpin
是一个双重否定”被带到面前,除了命名性状一般API规则,你能做的事使用它们,而不是您不能做的(正如我上面指出的,我认为Unpin
实际上遵循这两个规则,尽管意识到需要将“ Unpin”作为动词而不是将其作为形容词来听“不固定”,这对人们来说不直观。
@wmanley这不起作用,例如impl<T> Drop for Vec<T>
会因为我们没有Vec<T>: Unpin
而中断。 同样,即使在本主题中,也已经提出了沿这方面的建议。 请在回复之前阅读讨论。 我知道这提出了很多要求,但这是避免相同问题被一再解释的唯一方法。
我认为甚至有一个正式规则,即RFC决策只能基于公开已知的论点做出?
这被非正式地称为“没有新理由”规则。
不知道在哪里发布,但是有人可以看看https://github.com/rust-lang/rust/issues/56256吗?
与#56256相关, impl<T> From<Box<T>> for Pin<Box<T>>
未在OP中列出为稳定的,但一旦Pin
稳定后,它将隐式变为稳定。 是否还有其他不平凡的特质实现,应加以考虑以使其稳定? (扫描文档时,其他所有文档似乎都是委派给我的琐碎的委派实现)。
我们今天在libs小组中谈论了这个问题。 讨论的最后几周表明,仍然需要首先解决一些问题,尤其是在Unpin
的命名方面。
因此,我们暂时不会继续对此进行稳定(尽管有FCP)。
如果有人可以收集此主题中的想法并准备一个独立的提案以改善命名情况,将不胜感激。
@Kimundi
如果有人可以收集此主题中的想法并准备一个独立的提案以改善命名情况,将不胜感激。
这是否意味着libs团队不愿意按原样稳定当前的API? 我个人认为,没有人会提出比当前实施的名称更好的名称,因此我无法提出这样的建议,但是我非常在意这些API是否稳定,因此,如果来自libs团队的人有一个他们想要的名字,那么:shipit:
Bikeshed与一群同事一起使用了这个, @anp建议了DePin
,我实际上很喜欢,因为它删除了Unpin
的“ not pin”含义,并强调了它在谈论一种类型可以将其删除- Pin
'd。
@Kimundi ,您或libs团队中的某人使它脱离FCP,并更清楚地列出“应该解决的几件事”的含义?
@rfcbot关注取消命名
我不确定输入FCP后这是否真的有效,但是我想对Unpin
特性的命名提出正式的限制。 Unpin
特质对于API而言似乎非常关键,并且每次阅读时,“双重否定”(如上所述)都会使我大吃一惊。
关于各种名称的评论很多,但是不幸的是,我还没有为之感到兴奋。 我的“收藏夹”仍然沿用MoveFromPin
或Relocate
(我的天哪,这里有很多评论,我不知道该如何评论)。
我个人可以将Pin
本身以及Pinned
命名为未实现Unpin
特征的ZST。
我完全同意@alexcrichton的观点, Unpin
的命名是这里的主要争论点。 就我所建议的功能本身而言,我认为没有任何技术上的问题(尽管有很多评论,所以可能遗漏了一些内容)。
我仍然认为Pinned
是ZST的怪异名称,因为包含Pinned
的东西并不是真正固定的。.但这不是Unpin
。 PhantomPinned
(已在#55992中重命名)具有相同的问题,即当ZST大约Unpin
时,它指向Pin
Unpin
。
考虑到此功能的微妙之处,我仍然认为文档需要做更多的工作,但这可能不算是阻碍。
另外, @ Kimundi ,我很高兴看到libs团队愿意给它更多的时间来解决。 看起来好像很不必要,但我(以及其他人也)认为,提高此功能的可教性非常重要:)
同意@jonhoo,大约Pinned
仍然感觉很奇怪,并且PhantomPinned
并没有任何改善(它没有以任何方式固定,甚至没有以幻像的方式固定)。 我认为,如果我们找到了Unpin
的好名声,那么Pinned
就会自然地将自己重新命名为Not{NewNameForUnpin}
。
我真的不认为我们需要花更多的时间讨论PhantomPinned
-这种类型几乎不会出现在最终用户身上。 PhantomNotUnpin
/ PhantomNotDePin
/ PhantomNotMoveFromPin
/等不会对已经足够习惯的用户造成或多或少的影响该API可以合理使用PhantomPinned
。
一个简单的想法:特征Move
和ZST Anchor
。
每个类型为Move
能,除非它包含Anchor
,使得它贴到Pin
。
我喜欢Anchor: !Move
直觉上如何有意义。
我并不是建议我们专门花时间在PhantomPinned
,但我确实会保持开放的态度,因为这样做可能会降低Unpin
同样适用于PhantomPinned
。
我们已经介绍了Move
并解释了为什么它多次不合适。 所有类型都可以移动,直到被固定。 同样,以前曾建议使用Anchor
但从名称上并不清楚它是用来退出Unpin
ing / Move
ing的。
@stjepang我认为Move
早已被丢弃,因为某些东西是!Unpin
实际上并不能阻止它移动。 只有当类型在Pin
并且不是Unpin
时,您才有“合同义务”不移动它。
@withoutboats在原始评论中说:
Pin
包装器将指针修改为“固定”它引用的内存
我认为类型实现Unpin
很奇怪,因为值不是“固定”的,内存是固定的。 但是,谈论“可以安全移动”的值是有意义的。 如果我们将Unpin
重命名MoveSafe
怎么办?
考虑一下UnwindSafe
特性。 它只是“提示”标记特征,可以安全实施。 如果让!UnwindSafe
值越过catch_unwind
边界(带有AssertUnwindSafe
),您将通过使其不变量无效来“破坏”它。
同样,如果您具有!Unpin
/ !MoveSafe
值,则仍然可以移动它(当然),但是您将通过使其自引用无效来“破坏”它。 这个概念似乎很相似。
特质Unpin
实际上只是意味着MoveSafe
。 在我看来,这不是关于可以移出Pin
后面的内存的值。 相反,它是关于在移动它们时不会“破坏”它们的值。
MoveSafe
与Move
MoveSafe
具有相同的问题-可以安全地移动任何类型的所有值。 固定值后,才无法移动它。
MoveSafe
与Move
MoveSafe
具有相同的问题-可以安全地移动任何类型的所有值。
是的,但这是“安全”的不同含义,就像在UnwindSafe
。 无论如何,我可以使用Relocate
或类似的东西。
总而言之,我认为特征名称不应是DePin
, Unpin
或任何名称中带有“ pin”的东西。 对我而言,这是造成混乱的主要原因。 该特性并不是从Pin
的束缚中真正摆脱出来的-特性表示该值在移动时不会失效。
我只是将Pin
和Unpin
视为完全独立的事物。 :)
我感到完全相反;)。 该特征仅对Pin有意义,这是我们拥有的唯一一种有意义地表达对基础值的可移动性的约束的类型。 没有Pin,Unpin完全没有意义。
我喜欢将钉扎固定在固定板上。 如果卸下固定在板上的对象的引脚,则可以移动该对象。 卸下销钉为“不固定”。
我喜欢Unpin
这个名字。
我还可以看到!取消固定是双重否定,并且可能引起混乱。 但是,我想知道您需要多久编写一次!Unpin
。
我可以为Unpin想到的另一个名称是Detach
。 回到pinboard隐喻,您将不会_Unpin_,而是_Detach_从其对象中移除。
我想我真的很喜欢DePin
! 到目前为止,这是我的最爱-简洁明了,它显然是动词而不是形容词,并且!DePin
看起来也很清楚(“无法固定”)。
我认为类型实现Unpin很奇怪,因为值不是“固定”的,而是内存。
该值固定在内存中。 但价值在这里也至关重要。 对我来说,固定内存只是要确保它保持可取消引用状态,但不会被mem::swap
违反。 将值固定到内存意味着不要将固定的值移动到其他任何地方,这正是Pin
含义。
我还可以看到!取消固定是双重否定,并且可能引起混乱。 但是,我想知道您需要多久编写一次!
当您说您不会混淆名称时,我会听到您的声音。 我和该主题中的许多其他主题都让自己感到困惑。
我没有手头的代码,但是第一次尝试使用Future时,我想要一个函数来返回impl Future<Output=T>
。 我不记得发生了什么,但是我立即收到抱怨T和Unpin的粗糙编译器错误。 我需要回答的问题是“约束T仅松开是否安全”。 那导致我凝视深渊约两个小时的痛苦。
“哦,好的,如果那是Pin的意思。那么Unpin的意思是.. Box?为什么这是一个特性?”
“等等, impl !Unpin
?为什么不是impl Pin
?”
“没错,Pin和Unpin ...不是对立的。它们是完全不同的东西。等等,那么Unpin又是什么意思?为什么这样称呼它?”
“到底, !Unpin
是什么意思?不是……另一个不是钉子的东西?”
在我看来,唯一有意义的方法是用“可重定位”代替“取消固定”。 “类型无法在内存中重定位”是完全有意义的。 但是,即使知道Pin的作用,“类型仍未固定”仍然让我感到困惑。
将Unpin
重命名Relocatable
(或Relocate
)得到我的投票。 但是我发现其他所有建议都比Unpin更好。
该特征仅对Pin有意义,这是我们拥有的唯一一种有意义地表达对基础值的可移动性的约束的类型。 没有Pin,Unpin完全没有意义。
在这一点上,围绕pin的保证完全围绕某些行为,而不是类型。 例如,产生()
的生成器函数可以通过重复执行来简单地实现FnOnce
。 尽管此类型可能不会实现Unpin
-因为它的收益状态可以是自引用的,但它的FnOnce
接口(会自行移动)是完全安全的,因为当您将FnOnce
固定时,它尚未固定Unpin
特别是关于固定类型(即,移动它)后断言为安全的行为的类型,而不是有关该类型的某些固有属性。
讽刺的是我刚来到这里的评论,虽然Unpin
命名确实在过去一直争论不休,在它的辩论中,我记得见证,当我们选择了替代Move
与Unpin
,我想说的是明确的改进。 通常,人们只有在后来才意识到,尽管当前的设计是对先前设计的肯定改进,但仍存在进一步改进的余地。 这是我要从中得出的方向。
取消固定是专门针对在类型固定后(即移动它)被断言是安全的行为,而不是与该类型的某些固有属性有关。
固定时的行为是该类型的固有属性,就像共享时的行为/不变式一样。 我在此博客文章中对此进行了更详细的介绍。
@RalfJung我们正在互相交谈。 我看到很多困惑,自我参照类型“无法移动”-这是不准确的,它们具有可以进入的某些状态,在此期间它们不能被移动,但是当它们处于其他状态时,这是绝对安全的移动它们(我们的API依赖于移动它们的能力,例如将组合器应用于它们或将它们移动到Pin<Box<>>
)。 我试图澄清的是,这些类型“不能移动”并非如此。
啊,是的,那我同意。 !DePin
类型并不总是固定的,否则,它可以像其他任何类型一样移动。
@cramertj @withoutboats一件事我还没能赶上,你们都反对重命名Unpin
吗? 听起来大家似乎都不同意需要重新命名,但是我不确定您是否反对。
我个人认为这里的主要问题不在于名称Unpin
和“如果我们可以对其重命名,那么一切都将是直观的”。 虽然重命名可能会有所帮助(并且Relocate / DePin在这里看起来很不错...),但我认为主要的复杂性来自于固定自身的概念。 它当然不是最容易理解或解释的概念之一。 几乎没有。
因此,我认为core::pin
模块的文档需要_significantly_加强,并且需要包含更多示例。 好的示例将显示规范的用例以及不安全的不安全功能和实现的使用。
@alexcrichton我不反对重命名,不。 我认为Unpin
已经可以了,但是我可以接受DePin
,这就是我建议的原因。 RemoveFromPin
是最明显的“正确”字眼,但在语法上有些冗长,因此我想特别反对该名称。 但是,我反对无限期地推迟稳定,直到我们找到每个人都同意的名称才是最好的名称-我认为API具有一些固有的复杂性,由于Unpin
的名称,不会使它变得更好或更糟futures_api
周围的代码,文档和讨论更多动摇(因此我们可以开始逐步稳定futures_api
本身)。 也许我们应该安排一个专门的解决名字的VC会议,以便每个有意见的人都可以提出自己的解决方案,并且我们可以拥有更多的高带宽机会来解决这个问题?
我的投票是std::pin::Reloc
(重新分配)
我有两种非常假设的情况(嗯,实际上只有一种,但是有两种不同的执行方式),我不介意明确声明是否允许这样做。
Unpin
表示在固定类型状态下,从所有T
的状态(其中T: Unpin
)状态过渡很简单(我希望我能正确记住该术语(从@RalfJung的博客文章之一)到非固定的type-state。 这就是为什么Pin
可以发放&mut T
。
因此,假设我要创建一个类型Woof
的类型,也总是可以在固定类型下将其从所有Woof
状态转换为非固定类型-状态,但这样做并非易事,因此不能实现Unpin
,是否允许Woof
具有fn unpin(self: Pin<Box<Woof>>) -> Box<Woof>
?
与Meow
类型相同,该类型有时仅可能从固定变为未固定,并具有fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>>
。
我的2克拉自行车棚:
如果我理解正确,那么Unpin
真正的意思是“ Pin
对我没有影响”。
像BypassPin
或IgnorePin
呢?
因此,假设我要创建一个Woof类型,在固定类型状态下,也可以始终从其所有状态转换为非固定类型,但是这样做并非易事,并且因此不能实现Unpin,是否允许Woof具有fn unpin(self:Pin
>)->盒子 ?
是的,那应该可行。 例如,如果Woof
是侵入式链表的一个元素,则它可以提供一个从列表中删除该元素并同时删除Pin
的功能(对于非-使Woof
排队,这两个typestate是等效的,因此我们可以将其取消固定)。
Relocate
与RePin
问题相同,可能意味着该操作允许将值从一个固定的位置移动到另一个固定的位置,而不是完全取消固定该值。 它的含义不如RePin
强大,但仍然有些混乱。
这可能意味着允许将值从一个固定位置移动到另一个固定位置,而不是完全取消固定该值的操作。
尽管这并不是此特性的确切含义,但也并非完全错误:对于大多数用例,将值从一个固定位置移动到另一个固定位置就像自由使用它一样具有灾难性。 实际上,鉴于任何人都可以创建Pin<Box<T>>
,我什至看不到您为什么在这里做出根本的区分。
在大多数用例中,将值从一个固定位置移动到另一个固定位置就像自由使用它一样具有灾难性。
是的,这就是为什么具有将该操作添加到类型的特征会很有用的原因(这可能不是像Unpin
这样的标记特征,因为它可能需要执行诸如更新内部引用之类的操作)。 我不建议现在添加这样的内容,只是我可以看到将来(以std
或第三方提供)可能会导致名称的混乱重叠。
我现在不知道有任何强大的用例,但是我已经考虑过将类似的东西与固定集合一起使用。
好吧,这是一些想法。 最初,我想知道是否可以使用PinInert
或PinNoGuarantees
因为这也是描述,但考虑到我真正想要的是描述动作而不是
Unpin
一个问题(我不确定为什么)是,我似乎无法完全理解其意图的意思是“从大头针上拔下,松开的动作是可以安全执行的” ”。 原样传达了“放松的行为”动作,但是当我阅读它时,我似乎不太明白。 拥有Pin<T: Unpin>
感觉很奇怪,因为如果“取消固定”,为什么要在Pin上?
我想知道像CanUnpin
这样的名称是否可以工作? 其中很多与一种或另一种方式的硬保证无关(实现Unpin
并不意味着您将其从大头针上卸下,而只是意味着您可以从大头针上将其卸下)。 听上去怎么样? CanUnpin
对其他人足够可读? 够短吗?
(具有Can
的前缀也很容易向我传达了动词,它说您可以将其从大头针中删除,但不一定总是要这样做)。
作为另一个不相关的切线,我忘了提早提起(抱歉!)的一件事是,我认为上面的所有方法不一定都是固有方法。 我们已经在推理和冲突方法周围加载了大量的bug,并且在也实现Deref
类型上添加非常常见的名称(如as_ref
和as_mut
Deref
似乎是一个问题等待发生。
这些操作是否普遍发生,足以证明危险位置的合理性? (固有地)还是很少使用它们以使相关功能的安全途径受到影响?
自从我现在显然已经在这个游戏中有了皮肤: CanUnpin
似乎在精神上与Unpinnable
或类似的东西非常接近,而我的印象一直是,社区对这种修饰符不屑一顾特征名称,因为大多数特征都描述了类型可以采取的行动。 在这种意义上,隐含本身是隐含的“可以”或“可以”。 无论决定如何(在此处IM-not-so-HO范围内,名称都无所谓-很少有用户必须键入此名称),我鼓励大家对解决这里的问题感到有些紧迫感。 我为这种稳定感到兴奋,而且我知道很多人!
@alexcrichton
Unpin
(我不确定为什么)的一个问题是,我似乎无法完全理解其意图的意思是“从大头针上拔下,松开的动作是可以安全执行的” ”。
如果您认为T: Unpin
为“ T
不受Pin
”的影响,该怎么办? 然后,如果您有Pin<T: Unpin>
则意味着我们在Pin
内有一个不易固定的值,因此固定在这里实际上是没有意义的。
换句话说: Unpin
中和Pin
。 老实说,经过如此多的讨论,我已经对此进行了内在化,现在它才有意义。 😆
其中很多与一种或另一种方式的硬保证无关(实现
Unpin
并不意味着您将其从大头针中删除,只是意味着您可以_can_从大头针中将其删除)。
与此相对的是Send
特性。 这并不意味着您将发送该值,而只是意味着您可以发送它。
@anp我同意CanUnpin
不是一个好名字,我正在尽我最大的努力找出更好的名字! 似乎几乎没有人觉得需要重命名,因为所有建议似乎都因为基本相同的原因而被拒绝,我觉得Unpin
需要重命名。
还要提醒您,任何稳定措施都像其他所有更改一样,在下周发布时,肯定不会出现在该版本中,而下一个将成为下一个版本的候选者。 这意味着我们有7个星期(将近两个月)的时间着陆所有这些,以确保它尽快进入。 虽然我同意紧急性是必要的,但这只是“让我们不要忘记这种紧急性”,而不是“让我们在星期一之前解决这个紧急性”。
@stjepang我也Unpin
! 此时所有替代名称似乎都显得乏善可陈,因此我将要解决更好的文档问题。 我理想地希望找到对Pin
api不太了解的人,之后再阅读上述文档,但要仔细检查一下是否足以学习。
我也想继续正式地阻止改进文档的稳定性,因为这对这个问题尤为重要。
@rfcbot关注改进文档
具体来说,我认为需要更好的文档是:
P
的DerefMut
实现的“行为合理”的合同未记录在Pin::new_unchecked
。unsafe
函数都应该有一个代码示例,说明为什么它不安全,或者至少要对可能出错的步骤序列进行清楚的说明。Pin
是什么,以及它的含义。 我认为他们将从诸如“如何不是通用的不动产类型而是这种形式的某种形式”或“如何在实践中,例如在期货中使用Pin
”这样的信息中受益。Unpin
示例。 例如,听起来很多期货交易者都必须处理此问题,类似地,一些合并交易者特别要求这样做。 关于泛型如何工作以及它们如何发挥作用的一些示例用例可能会帮助您相当了解Unpin
。我也很好奇其他人是否还有可添加到文档中的具体可行项目!
接下来,我还想用as_ref
正式阻止self
问题,但是我怀疑这将很快解决。 (再次抱歉不记得提早提出该问题)
@rfcbot关注自我方法
提议将as_ref
, as_mut
, get_ref
, get_mut
, into_ref
和set
用作上的方法Pin
类型。 该提案提到,由于方法冲突,我们不对智能指针执行此操作,但引用表明经验表明,当今已实现的Pin
API不一致,并且最终往往会妨碍人们的前进。
但是,这些方法名称非常简短,甜美,在整个生态系统中都很常见。 过去,libs团队遇到了一系列永无止境的错误,在这些错误中,我们添加了trait实现等,这会导致与不同包装箱中现有trait方法的冲突。 这些方法由于其名称而显得特别危险。 此外,目前尚不清楚我们将来是否可以向Pin
中添加更多方法,即使现在最终确定出的名称将来添加的任何名称都具有很高的冲突风险。
我只想确保我们重新考虑与惯例之间的分歧,我个人特别担心后果,我认为如果能够找到避免这些危害的中间立场,那就太好了。
当我这样做时,我注意到的最后一件事,再一次,我认为这将很快解决
@rfcbot关注box-pinnned-vs-box-pin
Box::pinned
的名称Box::pin
被考虑了吗? 随着Pinned
和/或PhantomPinned
,如果名称可以与返回类型匹配,这似乎很简单!
@alexcrichton有什么特别的劝说你对MoveFromPin
作为一个选项? 我认为您较早就对此感到满意(而且很多人似乎也喜欢它)。 作为记录,我能记住或直接通过Ctrl + F-ing迅速发现的直接异议是“整个短语的名称...会很单调” (@withoutboats)并且“太罗y
我不得不承认,“经过这么多思考,我已经习惯了……现在”的动机使我颇为不安。 人类基本上可以习惯于事后合理化任何事。 无论名称是什么,这将是一个重大的惊喜,如果我们没有最终结束了习惯了。 (这就是为什么我们选择明显的-次优名称(例如existential type
作为临时名称的原因,以避免它们成为永久名称。
一不留神享有特权有经验的用户的角度(我们都是!)超过其他人谁将以事情更新鲜的大脑是未来是种错误,我认为我们需要对抗决策不断警惕的难,因为它是。
虽然它当然不是std lib的样式,但我还觉得CanUnpin
某种程度上使其更清楚地说明了该特定部分如何与其他部分配合。
在不阅读文档的情况下,不可能为初学者发明一个描述Unpin
含义的名称。 Send
和Sync
对于初学者来说也很难理解,但它们看起来和Unpin
一样简洁明了。 如果您不知道这些名称的含义,则必须阅读其文档。
@valff确实,这是正确的,但是有很多人已经阅读了文档并了解了固定的工作原理,但是Unpin
名字仍然给他们带来很大的心理困扰,而其他几个名字所造成的麻烦却更少。
@glaebhoerl哦,很抱歉,我个人比当前的Unpin
更喜欢MoveFromPin
或CanUnpin
的粉丝。 只是试图帮助得出一个结论!
我试图理解当前的建议,也许这种进度报告也可以在命名方面提供更多的启发。
Pin
的担保延伸到一个稳定的地址,直到Drop
对Pin
的创建者提出了额外的要求。 如果直接提到调用unsafe fn new_unchecked(pointer: P) -> Pin<P>
那些必要先决条件,我会觉得很有用。 了解如何创建Pin
有助于极大地了解其概念imo。we agreed that we are ready to stabilize them with no major API changes.
开头,其中包含许多旧的api。 同时,它还包含有关Drop
的大量见解以及相关的引脚图。 它还包含对本报告中未找到的要点一中提及的其他担保的明确表述。 过时的信息和新鲜的信息混合在一起会造成一些混乱,请考虑将所有信息移入本期或指出过时的段落。slight extension
,所以我希望有某种选择退出的担保。 由于钉住曾经在该指针上携带一个类型状态,直到被删除为止,因此有某种方法可以从该类型状态返回到“正常”状态。 实际上,这是我最初认为Unpin
要做的,但我认为Unpin
实际上要强一些。 它说,固定状态可以随意地变为非固定状态。 在这方面当前接口最小是可以的,但是Unpin
可能会与对该类型的操作混淆,该操作会删除一些需要固定状态的内部不变式(例如Drop
的软版本) 。说明:我个人比其他名称更喜欢MoveFromPin
,但可以说它是人为的和笨拙的。
除了@alexcrichton已要求的文档改进之外,想到的主要可操作的具体可操作项目将是解释map_unchecked
和map_unchecked_mut
图钉投影领域规则背后的原因。 类似于以下内容:
引脚保证在删除参考数据之前不会移动参考数据。 由于结构的字段在包含它们的结构之后被删除,因此它们的固定状态超出了结构本身的
Drop
实现。 投影到一个字段保证不会在struct析构函数中从其移出。
自动衍生标记特征的名称ElidePin
呢?
它会捕获该类型始终可以像被取消固定一样对待或表现。 !ElidePin
似乎也很清楚,尽管那可能是主观的。
标准标记也没有一个完美的名称来理解固定。 Pinned
似乎让人联想到包含结构本身是固定的,但是固定仅在显式操作之后才适用于指针。 这不是很关键,但也不是无关紧要的,特别是因为它可能是自引用类型的实现中首先遇到的类型。
我普遍反对MoveFromUnpin
/ RemoveFromUnpin
只是因为它们太罗word了,会退还手动Future
实现的人体工程学。 CanUnpin
/ DePin
都对我来说很好-我希望更清楚地是Unpin
遵循Read
/ Write
/等等。 但对人们来说似乎并不直观,因此我将为+1使其更清晰的内容看起来像语法。
我同意NotWhateverUnpinBecomes
可能是Pinned
的最佳名称。 也就是说, Adhere
意味着要注意和坚持。 :slightly_smiling_face:
CanUnpin / DePin对我来说似乎都很好-我希望更清楚地是Unpin遵循Read / Write / etc的模式
我认为使Unpin
变得困难的原因之一(不同于Read
是它是一个标记特征。 Read
很容易理解,因为有一种方法Read::read
-您需要知道的一切都在trait
。 如果x
是Read
我知道我可以打电话给x.read()
-类似地为Write
调用write
Write
,等等。很难解释X
实现Unpin
意味着Pin<Ptr<X>>
实现DerefMut
-这意味着您可以将其视为只是X
。
Read很容易理解,因为有一种方法Read :: read
如果只有我们可以在auto trait
定义+ GAT中添加始终适用的方法,那么我们可以拥有Unpin::unpin
- fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self>
)。 .....再三考虑,我认为这不会使任何人都不再困惑;)
(更严重的是,我会支持Pin::unpin
从Pin<P<T>>
变为P<T>
)
我会支持Pin :: unpin退出Pin
>至P
就像当前名称本身一样,这使我混淆了两个术语。 unpin
听起来很像是反转类型状态的保证,进行比较时就好像有方法fn unborrow(&'_ T)
或fn unmove(??)
。 由于pin
提供保证,直到表示类型的某些内存为Drop::drop
ed,我们才真正不反转状态,该类型仅保证所有其他表示都支持等效保证,因此我们可以忽略此。 这也是我看到的标记特征与io::Read
类的主要区别。 一个启用对编译器或语言的操作,而另一个启用对程序员的操作。
此外,重要的一点是,当前仅使用正确的键入就不能准确地表示出来。 调用操作unpin
听起来好像有一个逆函数pin
。 该函数还稍微错误地暗示了这种取消固定操作某种程度上与计算工作相关联,例如,在这方面,也应遵循更完善的命名约定来押注into_pointer()
。
最后,我认为类型特别有可能在计算工作中具有unpin
函数的类型。 具有非常特殊的内部不变性的类型可能能够以某种方式“修复”其内部状态,从而可以提供接口fn unpin(self: Pin<&'a mut T>) -> &'a mut
,在该位置它会终身放弃Pin
所有保证。 'a
。 在这种情况下,以上两点均不再适用。 可以设想这样的功能,就好像它在相同的存储位置中进行删除和重构(从而实际上删除了类型状态)具有同等的效果。 并且它可能涉及计算,例如通过将一些自引用移动到动态分配中。
令人困惑的是,如果混淆了名称,也会使库设计人员和实现者更难通过合并这两种思想来选择无混淆的名称。
我对MoveFromUnpin / RemoveFromUnpin的普遍反对是,它们过于罗word,会退缩手动Future实现的人体工程学。
我认为这不是真的,特别是对于MoveFromPin
,这对我来说似乎是合理的,而且无论使用哪种智能或哑自动补全功能,问题几乎都不存在。
人机工程学的重要部分还应该是代码的可读性和可理解性。 过去,Rust已因主动缩写( fn
, mut
等)而受到了一些批评,这使得某些代码难以阅读。 对于那些对于一个概念来说甚至更复杂且对大多数用户而言都满足特定目标的事物,使用更详细和描述性的名称应该是完全可以接受的。
@rfcbot解决命名取消固定
好吧,我已经炖了一段时间了,还亲自与@withoutboats进行了交谈。 我发现我现在至少对名称Unpin
作为标记特征感到很自在。 结果,我将删除一个有异议的异议(尽管如果libs团队中的其他人有不同的看法,请告诉我们!)
我之所以来到Unpin
主要原因基本上是上面已经说过的,这是我们能够想到的该特性的最惯用的名字。 我见过的所有其他特质名称都不符合Unpin
所能达到的惯用标准(至少在我看来)。
虽然我仍然相信“我刚刚看到了这个特征的名称,我想知道它的作用”的名字更好一些,但我认为这不是要解决的正确问题。 在这一点上我很清楚,至少在这个时候,我们无法为此特质找到一个不同的名称,这也是惯用的,但是我们正在转向解决不同问题的特征名称。 了解Unpin
就像Send
和Sync
,需要阅读文档才能完全理解正在发生的事情。
作为另一个澄清点,我列出了一些“阻止异议”,但它们本身就是TODO项而不是阻止异议。 我只是没有很好的方法来浏览这么长的线程! 因此,我认为在FCP失效一周左右的时间后,随时可以发布稳定的PR。 关于自我/固定/固定的最终要点可以在此处进行简要讨论(即使有必要,也可以将其保留为上述建议)。
在这种情况下,我认为尤其不是稳定文档的前提条件。 在这些类型稳定之前,我们有一个完整的周期(6周)来添加文档,而在整个异步/等待故事稳定之前,我们还有更长的时间来补充文档。 花费大量时间来改善现在的功能,现在的功能已经非常有用!
“惯用语”在这里甚至意味着什么? 关于Reloc[ate]
, Escape
, Evade
, Pluck
或Roam
有何
@alexcrichton是否有理由认为Unpin
比Depin
或DePin
更惯用?
我认为Depin
是一个非常可靠的选择,它不会像Unpin
一样给我造成精神上的困扰。
一个简单的原因可能是depin不是一个词,unpin是一个词。 就个人而言,我对理解Unpin
毫无困难。 我认为这与其他标记性状的命名相吻合。
@jimmycuadra Rust中有很多名称不是“真实的”单词,包括在stdlib中。
如果这被认为是选择一个名字而不是另一个名字的重要原因,我会感到惊讶。
@Pauan这是一个重要原因。 作为以英语为母语的人, Depin
对我来说听起来像我们忘记了取消固定的词存在并试图弥补这个词。 我在语法上明显地错了我:英语中的“ depinning”是“ unpinning”。
一个很好的类比是,如果我们有说“解锁”而不是“解锁”的API。
@jaredr by“ Read
和Write
特性。 我们有一个非冗长的名称,动词的约定,在可能的情况下尽量简短,并适合这种情况。
像您建议的名称都是可能的,但是Unpin
(或者至少让我觉得)最适合此特定操作(“您可以取消固定此类型”保证)。 其他人虽然是Unpin
松散同义词,但我认为很大程度上只是重命名了“ unpin”一词,有时并没有像Unpin
那样传达与Pin
相同的联系。做。
@Pauan我同意@withoutboats的观点, Depin
听起来不像Unpin
那样活跃。
@aturon在https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636中指出:
Pin
仅比&self
与&mut self
的选择有关的实现细节更多; 从长远来看,Pin
本身可能会成为具有语言支持的一种单独的引用方式。 在所有这些情况下,签名中传达的就是授予被调用者的权限,其中Pin
禁止从引用中移出。
表面上,这是指本机的&pin
类型或其他内容……但是idk这是如何与Pin<&mut T>
等等相乘的。 在我与@withoutboats尤其是@cramertj的对话中,他们根本不确定采用语言支持的单独引用模式的想法,以及如何从Pin<T>
到达那里。
在稳定pin
,最好先调和这些视图以确保我们在同一页面上。 关键的基础架构; 因此,我主要希望Aaron对此进行扩展,并希望小船和Taylor可以加入其中。
其他人虽然松散了Unpin的同义词,但我认为在很大程度上只是重命名了“ unpin”一词,有时并没有传达与Pin相同的联系。
@alexcrichton这实际上是一件好事,我想呢? 像Send
进行移动/复制(=)操作一样, Sync
进行借用(&)操作。 也许这样的联系实际上引起了更多的混乱?
@ crlf0710可能! 我不确定我本人是否会同意这种联系。 Send
和Sync
与它们正在启用的功能有关(将类型“发送”到其他线程并“同步”跨线程访问),并且在命名它们时,我们并未尝试避免将它们命名为接近一项操作或另一项操作
完全是@alexcrichton ! 因此,也许这个特征也应该与它的作用有关。 (“移动 (这里是一个动词)从Pin
)。 我不是英语母语人士,但我仍然认为从pin
“取消固定”有点...奇怪吗?
@ crlf0710但是该特征所实现的不是<moving>
出自Pin的<moving out of a Pin>
。 move和move的同义词存在的问题是它们隐含着特征控制类型完全移动的能力,而行为却没有。 与Pin的连接对于了解特质的实际作用至关重要。
因此,此特性最惯用的名称是一个动词,表示“移出别针”,对于我来说,“ Unpin”一词对我来说最明显。 这是维基词典中“ unpin”的定义:
- 通过取下大头针来松开。
- (传递,计算,图形用户界面)与先前固定的位置分离(图标,应用程序等)。
@withoutboats感谢您的解释! 实际上,我认为我可以接受这个Unpin
名称或锈小组最终决定使用的任何名称,我根本不希望阻止Pin类型的稳定性,并且我完全理解“移动”等问题。
只是感觉到某处有一点点“重复”。 我必须说服自己:这并不意味着“无法固定”,而是相反。 我想一段时间后,如果这是最终决定,我会习惯的。
同时,请允许我提出最后一个建议:实际上,我认为上述Detach
动词也很不错,尽管有点过于笼统。 如我所说,我不是母语人士,所以我不能为别人说话。 请只是将其视为一个小想法。
我当时在考虑安全的脱节固定投影,并提出了可能启用它的想法。 这是一个模拟与固定可变左值匹配
pin_proj! { let MyStruct { field1, field2 } = a_pinned_mutable_reference; }
将Pin<&mut MyStruct>
分解为对字段的固定可变引用。
为了使此安全和可用,我们需要另外两件事:
Kite
类型标记为“ always- Unpin
”字段Unpin
不安全为了投影的安全性,需要后者。 没有这个,我们可以安全地定义“带有固定投影的Kite
”,这实际上是错误的。
考虑到这一点,我建议在稳定之前使Unpin
不安全,以便为其他有用的操作留出空间。
@qnighy类型可能在其Drop
实现中违反固定保证,这使得Drop
等效于Unpin
。 使Unpin
不安全不会使任何其他代码安全,因为Drop
也是安全的,并且不能更改。 我们在跟踪问题上讨论了很多,并且在该主题中也提到了它。
从根本上讲,如果没有断言某些今天在Rust中无法表达的负边界的能力,就无法确保引脚的投影安全。
@ crlf0710
同时,请允许我提出最后一个建议:实际上,我认为上面提到的Detach动词也很不错,尽管有点过于笼统。 如我所说,我不是母语人士,所以我不能为别人说话。 请只是将其视为一个小想法。
分离似乎比“移动”和“重新定位”之类的名称要好得多,但它似乎与分离隐喻的其他可能用法冲突(类似于Escape如何与“ Escape Analysis”等冲突)。 关于数据如何与计算机科学中的其他数据建立联系的隐喻如此之多,这让我想到了Unpin的另一个新优势:通过紧贴“ pin”隐喻,它不会为未来的隐喻语言占据空间我们可能需要将其用于其他用途,例如“分离”或“转义”或“重定位”之类的名称。
@withoutboats我对这个自行车棚的名字或投资没有特别的偏好……但是我很好奇。 Detach
适合什么其他目的(如果您推测...)?
@Centril我没有明确的用例,但是我可以想象一些与解构有关的用例。
@withoutboats是的,这很有道理; 干杯!
@withoutboats我不确定Wiktionary条目是否是证明Unpin
命名以启用move from a pin
的最佳动机。 物理隐喻的隐喻在于,在Rust中移动并不能夺走世界上的物体。 更准确地说,“固定”固定的指针并不能控制引用的内存,它的分配和有效性仍然是指针语义所要求和保证的,因为对象表示为T
。 因此,取消固定不会像取消装箱那样提供T
的完全受控表示。 而且,根据moving
定义unpinning
的字典条目实际上是混淆的一部分,而不是这种名称的合理性。
在提出的API中,没有一种方法具有这种效果(并且也不在pin的范围内)。 我没有看到安全的方法,例如将Pin<Box<T>>
为Box<T>
(并扩展为T
),也不确定Unpin
是否应该有如此强大的可能性。 我不确定所有差异在哪里。 但是据我所知, Pin
适用于某些内存位置,而Unpin
保证T
表示中的任何内容都不依赖于引脚保证。 但是,这是否等同于可以完全删除并忘记内存? 我觉得不是。 我会想到一个更具体的例子。
编辑:更具体地讲,即使T
是Unpin
,在释放该内存之前,我是否可以依靠已固定的内存上调用T::drop(&mut)
某个实例? 从形式主义的角度来看,答案应该是肯定的,但是名称Unpin
向我传达了相反的意思。
编辑2: Rc
允许观察Pin<&T>
但对于T: Unpin
仍然没有在原始存储位置调用drop
。 使参考保持在图钉之外,然后将图钉放下后,您可以使用Rc::try_unwrap
移出。 https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca可以通过现有机制有效回答该问题,但是否可以按预期工作?
也许IgnorePin
? 如果T
是IgnorePin
,则可以将Pin<Box<T>>
视为&mut T
实际上忽略了Pin
(AIUI也忽略了Box
)。 要忽略的是一个动词,我猜不是IgnorePin
,但是我不确定它是什么。 IgnorePin
描述它所允许的内容,不是描述对类型的约束,但是Unpin
都不是。
@wmanley在上面的某些评论中,我有一个非常相似的想法ElidePin
,尽管那时我还不能具体表达出为什么这个感觉更精确。 但是我同意“一个动词”是Rust标记特征的样式指南。 即使它也允许以!ElidePin
/ !IgnorePin
的形式进行更自然的求反,它也不是最优的。
@withoutboats跟进问题:由于引脚似乎是根据基础内存指定的,因此引脚如何与ZST交互? 由于Pinned
是ZST,因此即使ZST也可能是Unpin
。 我可以凭直觉地说,它的内存表示形式不会正式失效,因此永远不需要调用T::drop(&mut self)
,这与从泄漏的Box
到&mut 'static _
内存中构建销钉的方式非常相似Box
。 同时,这可能都是错误的,我知道如何进行另一种解释。 我觉得这些设置值得文档注意。
如果可以确保没有观察到固定现象,那么使用T: !Unpin
创建一个Pin<P<T>>
并立即取消固定该值是否安全? 如果固定值调用了类似轮询的方法后才移动固定值,这是否只是未定义的行为?
@HeroicKatora不幸的是,由于泛型,今天有可能为!Unpin
类型实现Drop
,例如:
struct S<T>(T); // `!Unpin` if `T: !Unpin`
impl<T> Drop for S<T> { ... }
@cramertj谢谢,也意识到了这一点。
@cramertj因此,我们默认泛型边界为T: ?Unpin
吗? 对于诸如Sized
类的现有类型,不默认为T: Unpin
背后还有其他原因吗? 它将给出一些令人讨厌的严格示例,但是该附加自动绑定会导致回归吗?
@HeroicKatora这将要求在标准库中的每种类型以及一堆根本不关心Unpin
的代码中撒上?Unpin
界限。
添加PhantomPinned字段也是安全的,这是创建Drop +!Unpin类型的另一种方法。
没有默认设置为T:为现有类型取消固定还有其他原因吗?
取消固定只是“发送和同步”之类的自动特征,此建议不涉及任何新的语言功能。 因此它具有与已经定义的自动特征相同的语义,这是默认情况下它们不应用于泛型(与Sized不同,后者是编译器的内置特征,而不是自动特征)。
如果固定值调用了类似轮询的方法后才移动固定值,这是否只是未定义的行为?
在这里我们应该清楚,在这种情况下未定义的行为实际上不是传统的UB。 而是,其代码(观察Pin中的类型)可以对该值的所有权语义进行假设(即,如果未实现Unpin,则直到其析构函数运行之后,它才会再次移动或无效)。 从编译器认为它永远不会发生的意义上说,它不是UB(编译器不知道Pin是什么)。
是的,从某种意义上讲,您可以验证没有代码依赖于您提供的保证。 但是您所做的当然也毫无意义,因为没有代码可以观察到该类型是固定的。
@cramertj我知道,这有点不幸。 我对规范中的Drop
与Pin
交互而不是在语言中感到不舒服。 接口正确性,可用性和在不久的将来发布之间存在很大的冲突。 我可以对上面的Edit 2进行澄清吗?
Rc允许您观察Pin <&T>,但对于T:取消Pin仍然没有在原始内存位置调用drop。 使参考保持活动状态,然后将其放置在引脚上,然后使用Rc :: try_unwrap移出。 https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca
@HeroicKatora您的代码的问题是Mem<'a>: Unpin
,但是您使用的不安全代码行依赖于仅适用于未实现Unpin
类型的假设。 我已经编辑了要点,以包括证明Mem<'a>: Unpin
: https : =201d1ae382f590be8c5cac13af279aff
调用Pin::new
,您依赖Unpin绑定,该绑定只能在目标实现Unpin的指针上调用。
您认为已经发现的漏洞已得到考虑,这就是为什么从Rc<T: ?Unpin>
到Pin<Rc<T>>
没有办法的原因。 您必须使用Rc::pinned
构造引脚中的Rc。
@withoutboats只是想确认一下,在无效之前, T: Unpin
确实也选择退出drop
调用。 这就引出了一个问题,是否也应该没有
fn into_inner(Pin<P>) -> P where P: Deref, P::Target: Unpin;
因为它不保护接口的任何部分,所以将Pin<Box<T>>
为Box<T>
可能很有用(当然是T: Unpin
)。
@HeroicKatora,您认为该功能很安全。 我不介意加入,但我想避免扩大我们在这一刻稳定API,因为该线程已数百条评论长。 我们可以随时根据需要添加它,就像处理所有标准API一样。
我只想说,这两个命名和功能Unpin
使得与逆向方法很多感知感。 而且知道Pin
保证确实仅限于T: !Unpin
。 这种方法的存在也将直接证明这一点,而不是构造逃生舱口以显示可能性:smile:。 并且其文档将是解释(或再次链接到)这些限制的理想场所。 (甚至可以考虑将其命名为unpin
而不是into_inner
)。
编辑:它主要只是概括已经存在的内容。
impl<P> Pin<P> where P: Deref, P::Target: Unpin
fn unpin(self) -> P
具有P=&'a mut T
的实例化,这与建议的等效
impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin
fn get_mut(self) -> &'a mut T
既然稳定已经过去,PFCP的意义似乎还没有定论,因此:
@rfcbot取消
是否有任何跟踪以确保从https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982开始的注释被转化为文档? 似乎我们已经为时已晚,因为beta分支了。。。即使在稳定化之前明确要求在此发布它:/
有什么理由仍然开放吗? @Centril,您关闭并重新打开了它,这是故意的吗?
@RalfJung我试图取消FCP,但它没有收听; @alexcrichton您可以取消FCP吗?
这很稳定,所以关闭。
最有用的评论
是否有人在写关于期货的经济学标章? 考虑到这是多么微妙,这似乎是非常必要的。 特别是我认为,示例,尤其是错误的/错误的实现示例以及它们的不完善示例,将很有启发性。