为什么期货在Rust中使用大头针?

owfi6suc  于 2023-01-30  发布在  其他
关注(0)|答案(2)|浏览(98)

我知道pin用于将数据固定到一个内存中。当我在Future trait中使用poll()方法时,它会被连续调用,直到它返回Poll::Ready。使用pin是为了确保在调用poll()时将数据放置在同一内存中吗?换句话说,它是否用于防止编译器在调用poll时移动可能发生内存移动的代码(生成编译错误)?

rbpvctlc

rbpvctlc1#

不会。编译器不会背着您移动数据。Pin不是语言保证,而是库保证
构造Pin有两种方法:

  • 不安全。
  • 如果数据为Unpin

这确保了不安全的代码可以依赖于固定保证。Unsafe code can never trust foreign safe code. It can only trust known safe code (such as std, or code inside its crate), or (even) foreign unsafe code。这是因为如果不安全代码依赖于外来安全代码的保证,则可能导致安全代码的UB。(带入链接的nomicon)是BTreeMapOrdBTreeMap要求项目具有总排序,但是它的不安全代码不能依赖于此,并且即使在存在非全排序的情况下也必须表现良好。这是因为Ord实现起来是安全的,因此可以用不遵守全排序规则的安全代码来实现它,并且与BTreeMap一起只使用安全代码就导致未定义的行为。(例如i32,我们知道它正确地实现了Ord),或者BTreeMap需要unsafe trait UnsafeOrd而不是Ord,我们可以相信这一点,因为违反X1 M13 N1 X的契约是作为X1 M14 N1 X的特性来实现的未定义行为。
假设我们是一个自引用的future,我们必须确保我们在内存中保持相同的位置,否则我们的自引用将是悬空的。因为悬空引用是UB,这必须包含不安全的代码。我们可以使poll()unsafe fn,但这是不方便的-这意味着轮询future是不安全的。相反,我们需要Pin<&mut Self>
现在请记住有两种方法来构造Pin。如果我们是Unpin,这意味着我们不是自引用的--也就是说,可以安全地移动--因此我们可以安全地构造Pin。另一方面,如果我们是自引用的,我们就不应该是Unpin。构造Pin的唯一方法是使用unsafe方法new_unchecked(),它的安全前提条件要求固定的数据永远不会被移动。因为这个方法是不安全的,所以使用它需要不安全的代码,所以我们可以依赖它的保证(记住我们可以信任外来的不安全代码)。
这并不意味着new_unchecked()是构造Pin<NonUnpin>的唯一方法,Rust中的一个常见模式是拥有一个底层的不安全机制,该机制允许一切(只要它是合理的),但不验证任何东西,然后通过限制一些能力在其上构建各种安全抽象,一个常见的例子是内部可变性:我们有UnsafeCell,它是不安全的,只要你遵守别名规则,它就允许任何东西,我们在它上面有多个安全抽象,每个抽象都通过一些限制来保证安全性:

  • Cell用于Copy类型并且是非线程安全的,以及原子类型,其通过被限制为特定的类型集和原子操作来保证安全性。
  • RefCell,其通过运行时检查来保证安全性,与UnsafeCell一样灵活,但具有运行时成本。
  • MutexRwLock,它们通过阻塞来保证安全性。
  • x1E6 F1 X和x1E7 F1 X,它们通过仅可写一次(并且对于线程安全版本,可能阻塞)来保证安全性。

Pin使用相同的模式:我们有Pin::new_unchecked(),它是unsafe,但有多个抽象,如Box::pin()(需要装箱)或pin!()宏(或板条箱中的稳定版本),它们通过仅允许本地钉扎来保证安全性。

ff29svar

ff29svar2#

Rust std库的作者将future mutable reference固定在Futures::poll中,因为他们希望为自己的库以及那些与他们的库紧密相关的库提供可靠性保证。他们希望poll的定义有助于他们的可靠性保证。内存不会被破坏,除非在某个地方错误地使用了 unsafe
当future是自引用的,因为它希望设置一个自引用,以便稍后由后续轮询使用时,这是有效的,因为到目前为止构建的异步运行时理解,一旦future被轮询至少一次,它们就不能移动它。
但是,如果future的地址没有在编译时被强制固定,那么一个天真的用户可能会创建一个库的future,然后自己重复调用future的poll方法;如果他们的代码在调用poll之间移动了future,那么他们就会为poll方法提供机会,使其解引用一个不再属于future的地址;他们将得到未定义的行为,而不是每次调用 unsafe
未来的地址必须被固定才能调用poll方法,这意味着调用者必须使用 unsafe。根据定义,固定地址涉及使用 unsafe -这就是为什么存在固定的想法。
因此,一个用户,不管是否天真,在编写调用poll的代码时都不会编译代码,除非他们自己使用了 unsafe。他们可能错误地使用了 unsafe,所以在poll逻辑中仍然可能存在一个受损的内存地址,但不会违反可靠性保证-内存不可靠是通过误用 unsafe 创建的。而不是通过误用安全功能或方法。

相关问题