Skip to content

Instantly share code, notes, and snippets.

@alexcrichton
Created November 9, 2018 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexcrichton/b63d017ca6f74e039bad905130f5a5f3 to your computer and use it in GitHub Desktop.
Save alexcrichton/b63d017ca6f74e039bad905130f5a5f3 to your computer and use it in GitHub Desktop.
  • fn new(P) -> Pin<P> where P: Deref, P::Target: Unpin

    • This is a safe method to construct Pin<P>, and while the existence of Pin<P> normally means that P::Target will never be moved again in the program, P::Target implements Unpin which is read "the guarantees of Pin no longer hold". As a result, the safety here is because there's no guarantees to uphold, so everything can proceed as usual.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • Unlike the previous, this function is unsafe because P doesn't necessarily implement Unpin, so Pin<P> must uphold the guarantee that P::Target will never move ever again after Pin<P> is created.

      A trivial way to violate this guarantee, if this function is safe, looks like:

      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
      }
      

      Consequently it's up to the user to guarantee that Pin<P> indeed means P::Target is never moved again after construction, so it's unsafe!

  • fn as_ref(&Pin<P>) -> Pin<&P::Target> where P: Deref

    • Given Pin<P> we have a guarantee that P::Target will never move. That's part of the contract of Pin. As a result it trivially means that &P::Target, another "smart pointer" to P::Target will provide the same guarantee, so &Pin<P> can be safely translate to Pin<&P::Target>.

      This is a generic method to go from Pin<SmartPointer<T>> to Pin<&T>

  • fn as_mut(&mut Pin<P>) -> Pin<&mut P::Target> where P: DerefMut

    • I believe the safety here is all roughly the same as the as_ref above. We're not handing out mutable access, just a Pin, so nothing can be easily violated yet.

      Question: what about a "malicious" DerefMut impl? This is a safey way to call a user-provided DerefMut which crates &mut P::Target natively, presumably allowing it to modify it as well. How's this safe?

  • fn set(&mut Pin<P>, P::Target); where P: DerefMut

    • Question: Given Pin<P> (and the fact that we don't know anything about Unpin), shouldn't this guarantee that P::Target never moves? If we can reinitialize it with another P::Target, though, shouldn't this be unsafe? Or is this somehow related to destructors and all that?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • This is an unsafe function so the main question here is "why isn't this safe"? An example of violating guarantees if this were safe looks like:

      ...

      Question:: what's the counter-example here? If this were safe, what's the example that shows violating the guarantees of Pin?

  • fn get_ref(Pin<&'a T>) -> &'a T

    • The guarantee of Pin<&T> means that T will never move. Returning &T doesn't allow mutation of T, so it should be safe to do while upholding this guarantee.

      One "maybe gotcha" here is interior mutability, what if T were RefCell<MyType>? This, however, doesn't violate the guarantees of Pin<&T> because the guarantee only applies to T as a whole, not the interior field MyType. While interior mutability can move around internals, it still fundamentally can't move the entire structure behind a & reference.

  • fn into_ref(Pin<&'a mut T>) -> Pin<&'a T>

    • Pin<&mut T> means that T will never move. As a result it means Pin<&T> provides the same guarantee. Shouldn't be much issue with this conversion, it's mostly switching types.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> means that T must never move, so this is trivially unsafe because you can use mem::replace on the result to move T (safely). The unsafe here "although I am giving you &mut T, you're not allowed to ever move T".
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>

    • I think the unsafe here is basically at least the same as above, we're handing out &mut T safely (can't require an unsafe closure) which could easily be used with mem::replace. There's probably other unsafety here too with the projection, but it seems reasonable that it's at least unsafe because of that.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • By implementing Unpin a type says "Pin<&mut T> has no guarantees, it's just a newtype wrapper of &mut T". As a result, with no guarantees to uphold, we can return &mut T safely
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • This can be safely implemented with as_ref followed by get_ref, so the safety of this impl follows from the above.
  • `impl<P: DerefMut> DerefMut for Pin

    where T::Target: Unpin { }

    • This can be safely implemented with as_mut followed by get_mut, so the safety of this impl follows from the above.
  • impl<T: ?Sized> Unpin for Box<T> (and other pointer-related implementations)

    • If no other action were taken, Box<T> would implement the Unpin trait only if T implemented Unpin. This implementation here codifies that even if T explicitly doesn't implement Unpin, Box<T> implements Unpin.

      Question: What's an example for what this is fundamentally empowering? For example, what safe could would otherwise require unsafe if this impl did not exist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment