@RalfJung SelfReferential
should implement !Unpin
, shouldn’t it? Unpin
is an auto trait, and there are no negative impls for Unpin
in the standard library (as of right now), so SelfReferential
implements Unpin
by default. Thus, I can cause undefined behavior in safe code with this type:
fn main() {
let mut s = PinBox::new(SelfReferential::new());
s.as_pin().init();
println!("{:p} {:p}", &s.data, s.self_ref); // same address twice
println!("{:?}", s.as_pin().read_ref()); // prints Some(42)
let mut s = *Into::<Box<SelfReferential>>::into(s);
let s = Pin::new(&mut s);
println!("{:p} {:p}", &s.data, s.self_ref); // different addresses now!
println!("{:?}", s.read_ref()); // reads from freed heap memory!!
}
@withoutboats I think raw pointers should implement !Unpin
so that mistakes like this don’t slip by accidentally. Otherwise, the pinning API is “unsafe by default”, which I believe goes against the spirit of Rust. What’s unfortunate though is that there are very few hand-written self-referential structs, so most types with raw pointers would have to implement Unpin
explicitly to become compatible with pinning and, by extension, futures. Still, types made of raw pointers are still relatively rare, so perhaps it’s not so bad.
Another option is to rely on a lint to catch the mistake; the lint would detect when a field of a struct that is a raw pointer is assigned a pointer (directly or indirectly) into the struct, and suggest that the type should implement !Unpin
. If we choose this option, this lint ought to be in rustc (though its life could start in Clippy) so that everyone who needs to see it does see it. A problem with this lint is that there might be false positives, especially with indirect pointers. For example, consider this struct:
pub struct Foo<T> {
items: Vec<T>,
current: *const T, // points into items
}
It’s perfectly safe to move a Foo
, but the lint would think that this type should implement !Unpin
, even though it’s not necessary.