Cannot assign to data in a dereference of `Pin<&mut MyFutureImpl<T>>`

I'm practicing to implement a Future, and I wrote like this:

pub struct MyFutureImpl {
    waker: Option<Arc<Mutex<Waker>>>,

}

impl Future for MyFutureImpl {
    type Output = Arc<i32>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.waker = Some(Arc::new(Mutex::new(cx.waker().clone())));
        Poll::Pending
    }
}

The code makes no sense, but it can be compiled, but if make the MyFutureImpl a generic, it won't compile:

pub struct MyFutureImpl<T> {
    waker: Option<Arc<Mutex<Waker>>>,
    _marker: PhantomData<T>,
}

impl<T> Future for MyFutureImpl<T> {
    type Output = Arc<i32>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.waker = Some(Arc::new(Mutex::new(cx.waker().clone())));
        Poll::Pending
    }
}

the compiler said:

error[E0594]: cannot assign to data in a dereference of `Pin<&mut MyFutureImpl<T>>`
  --> src/main.rs:16:9
   |
16 |         self.waker = Some(Arc::new(Mutex::new(cx.waker().clone())));
   |         ^^^^^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Pin<&mut MyFutureImpl<T>>`

Honestly speaking, I didn't figure out what's the difference with or without a generic, and I don't know how to fix this (don't know whether or how to impl DerefMut for Pin<&mut MyFutureImpl<T>>)

You can add

impl<T> Unpin for MyFutureImpl<T> {}

(the default is that T is required to be Unpin in the auto-impl of Unpin)

1 Like

I'm sorry I don't understand pin and unpin very well, for now, does this

impl<T> Unpin for MyFutureImpl<T> {}

has any dangerous effect?

Its effect is that it makes some types of unsafe code that you could otherwise have written incorrect. If you have no unsafe code in the future implementation, then it is not dangerous.

1 Like

Thanks for helping, I will take your suggestion and investigate Pin further.

What is the type parameter T used for? PhantomData<T> is mostly meant for types that are actually owning a value of type T. (Often useful when dealing with unsafe code / raw pointers / etc…) If you just need a type parameter e.g. as a marker type that implements some traits, or for other reasons that don’t imply ownership of a T value, consider using either

  • PhantomData<fn() -> T>, or
  • PhantomDara<fn(T) -> T> / PhantomDara<fn() -> Cell<T>> (if you want an invariant parameter)

instead. Using either of these will remove unnecessary bound on all kinds of auto-traits that you’d otherwise get, i.e. also on Send/Sync, or [Ref]UnwindSafe.

FYI, this is not as common knowledge as one would hope for; I recently made a PR on the standard library to use this pattern on 3 types including e.g. Pending in std::future - Rust. Compare with the stable docs, and see how the auto trait implementations are relaxed, and Unpin doesn’t need a manual impl anymore.

Of course, if this is just example code for a case where you actually do own a T in the future, then implementing Unpin is of course a legitimate reason. Read more on “structural pinning” in the standard library docs for the implications of such an implementation.

If you do want to keep a field of type T structurally pinned but mutate a different field (like your waker here), then pin-project can be a useful tool to avoid the need for unsafe code.

3 Likes

I would say my type indeed not own anything relative to a T, I only use T for trait things (associated functions), your suggestion works, and really makes sense, but I got a question:

If I use PhantomData<fn() -> T> instead of PhantomData<T> and if one day I do own some T, for example a filed called ptr: *const T, and I forgot to change PhantomData<fn() -> T> back to PhantomData<T>, won't this cause a problem (IIRC, the drop thing), and the compiler will not warn me about that?

As far as I know, PhantomData<T> is only relevant for dropck if you use (the unsafe unstable attribute) #[may_dangle] somewhere. Don’t ask me how exactly it’s relevant in that case, that’s something I still want to learn/study in detail at some point in the future.

So under normal circumstances, PhantomData only influences variance and auto-traits. So the worst thing you can get from PhantomData<fn() -> T> instead of PhantomData<T> is an unsound Send/Sync implementation. (Of course you’d have to be using unsafe code somewhere for them to be unsound. And if you have a pointer type like *const T in a field, then Send and Sync no longer implemented by default for your struct anyways.)

According to the phantom data illustrated in Rust Nomicon, it didn't say anything about #[may_dangle] when it comes to the dropck, but I'm not an expert here... The Sync/Send seems not a problem here, but it scares me if the drop check not works...

Right, I only recently learned this, too, in this discussion


1 Like

I'm sorry I don't quiet understand

(the default is that T is required to be Unpin in the auto-impl of Unpin )

I checked that the trait Unpin is an auto trait, so why doesn't it implement my type MyFutureImpl<T>?

It does. It's just that the auto-trait gives you this:

impl<T: Unpin> Unpin for MyFutureImpl<T> {}

We also want MyFutureImpl<T> to be Unpin even if T is not, so we override the default.

It is definitely not what I expect on how an auto trait works, are there any doc about the rules on how auto trait works? I thought the auto trait should do something like

impl<T> Unpin for MyFutureImpl<T> {}

But clearly I'm wrong...

More accurately it gives something like

impl<T> Unpin for MyFutureImpl<T>
where
    Option<Arc<Mutex<Waker>>>: Unpin,
    PhantomData<T>: Unpin,
{}

for

pub struct MyFutureImpl<T> {
    waker: Option<Arc<Mutex<Waker>>>,
    _marker: PhantomData<T>,
}

i.e. all the fields’ types are listed. (Note that the automatic / implicit auto-trait implementations can refer to private types in fields, something that ordinary trait implementations can’t do.)

Now, PhantomData<T> implements Unpin exactly when T does, and Option<Arc<Mutex<Waker>>> always implements Unpin, so the result is equivalent, and rustdoc is nice enough to present a simplified version of the implementation reduced exactly to that equivalent T: Unpin bound.


This is also why fn() -> T makes a difference. It always implements Unpin, even when T doesn’t. (The reason I’m using fn() -> … is because it’s really the only (covariant) type in the standard library that does this.)

1 Like

You mentioned rustdoc, isn't the code

impl<T> Unpin for MyFutureImpl<T>
where
    Option<Arc<Mutex<Waker>>>: Unpin,
    PhantomData<T>: Unpin,
{}

generated at compile time? Do you suggest that you guys really read the generated code somewhere created by rustdoc?

I assume that @steffahn just wrote that down based on knowledge of how the compiler works. I don't think rustdoc outputs that anywhere.

I’m not sure how the compiler works internally, it might very well be the case that no instance is “generated” at all. Certainly there’s no “actual” code-generation going on. Rustdoc does show you the “Auto Trait Implementations” section for each documented type, and that section would render something that says “impl<T> Unpin for MyFutureImpl<T> where T: Unpin” for you. But the actual logic of auto traits is: If all the fields implemente it, then the struct implement it, too, and the implementation

impl<T> Unpin for MyFutureImpl<T>
where
    Option<Arc<Mutex<Waker>>>: Unpin,
    PhantomData<T>: Unpin,
{}

is the natural straightforward representation of what this rule means. Just if rustdoc displayed it this way, it would display a lot of implementation details (i.e. the types of all the private fields), so it is a good thing that rustdoc somehow – probably with some magical “compiler support” – can give you the simplified, equivalent represenation with a where T: Unpin clause.

Auto traits

The Send, Sync, Unpin, UnwindSafe, and RefUnwindSafe traits are auto traits . Auto traits have special properties.

If no explicit implementation or negative implementation is written out for an auto trait for a given type, then the compiler implements it automatically according to the following rules:

  • &T, &mut T, *const T, *mut T, [T; n], and [T] implement the trait if T does.
  • Function item types and function pointers automatically implement the trait.
  • Structs, enums, unions, and tuples implement the trait if all of their fields do.
  • Closures implement the trait if the types of all of their captures do. A closure that captures a T by shared reference and a U by value implements any auto traits that both &T and U do.

For generic types (counting the built-in types above as generic over T), if a generic implementation is available, then the compiler does not automatically implement it for types that could use the implementation except that they do not meet the requisite trait bounds. For instance, the standard library implements Send for all &T where T is Sync; this means that the compiler will not implement Send for &T if T is Send but not Sync .

Auto traits can also have negative implementations, shown as impl !AutoTrait for T in the standard library documentation, that override the automatic implementations. For example *mut T has a negative implementation of Send, and so *mut T is not Send, even if T is. There is currently no stable way to specify additional negative implementations; they exist only in the standard library.

Auto traits may be added as an additional bound to any trait object, even though normally only one trait is allowed. For instance, Box<dyn Debug + Send + UnwindSafe> is a valid type.

In particular, note the line

Structs, enums, unions, and tuples implement the trait if all of their fields do.


Beyond the wording in the reference, it appears as if PhantomData also implements auto traits based on compile-built-in rules, and async { } blocks as well as futures returned by async fn act like closures.

I'm sorry I got too many questions after knowing the detail of auto impl.

If I understand right, A Pin<&mut T> means that the pointee T will never moved, so it's safe to do some self-referenced stuff with a Pin<&mut T>.

In my case, I just what to assigned a new value to a field, I didn't move MyFutureImpl at all. And what confused me is that the self is a Pin<&mut self>, but trait DerefMut is not implement on MyFutureImpl.

Would you kindly help to expain what's the relationship between Pin and DerefMut?

From the compiler’s standpoint, the only relationship is this implementation.

As a follow-up question, we could discuss why the implementation is the way it is.