I want to have a weak pointer that can point to various values that implement a trait. A variable declared as let mut weak_ptr: Weak<dyn MyTrait> works for this. Let's say I want to first initialize it to be an empty weak pointer. The following do not work:
//let mut weak_ptr: Weak<dyn MyTrait> = Weak::new(); // error: cannot infer type for `T`
//let mut weak_ptr: Weak<dyn MyTrait> = Weak::<dyn MyTrait>::new(); // error: function or associated item not found in `std::rc::Weak<dyn main::MyTrait>`
//let mut weak_ptr: Weak<dyn MyTrait> = Default::default(); // error: doesn't have a size known at compile-time
Explicitly specifying the generic type argument as some struct that implements the trait works:
struct ArbitraryStructThatImplTrait;
impl MyTrait for ArbitraryStructThatImplTrait { }
let mut weak_ptr: Weak<dyn MyTrait> = Weak::<ArbitraryStructThatImplTrait>::new()
I believe this is because both Weak::new() and Weak::default() are declared with impl<T> without where T: ?Sized, so the type argument cannot be a dynamically sized type like a trait object.
However, I see no reason why Weak::new() and Weak::default() can't work for dynamically-sized types too. Empty weak pointers of any type work the same way, and the implementation of Weak::new() simply casts the max integer to a pointer -- it doesn't store anything that depends on the type T. I can already create an empty weak pointer of a struct that implements the trait, and assign that to a weak pointer of the trait, so I don't see why I shouldn't be able to create the empty weak pointer of the trait directly.
Hmm... that seems kinda silly as an empty weak pointer will never use a vtable of T. Could this be made to work if Weak were internally implemented with an Option, with empty pointer being None?
Yes, but then you would lose the non-null optimization and pay that performance penalty on every use even if that cost is unnecessary. You can wrap it in Option instead, and use None to represent not initialized
The think reason for this has to do with CustomDsts. @RalfJung should know more about why pointer metadata should always be valid even if the pointer is invalid.
This is an interesting question! I think this is the first use-case I see for a fat reference (not a raw pointer) to carry "bogus" metadata. The reason we require it to be valid is mostly that when i doubt, it seems produent to have a stronger invariant.
Turns out I was too hasty in my previous comment -- Weak is internally NonNull which is a raw pointer. So this is "just" a raw pointer with invalid metadata, which I would be rather surprised if it caused UB. But we do not have a definite statement allowing this either.
IIRC, originally Weak::new() actually allocate a memory as Arc::new() does but never initialize it. I remember that this behavior was stated at the stdlib document, but it's changed anyway.
If the vtable cannot be accessed (the pointer is invalid, dereferencing is UB), for what reason would just using a dangling vptr or even just &[0; N] be wrong?
Even if we require the vtable ptr in *const dyn _ to be nonnull, I see no reason to require it to point to an actual vtable (yet). And even if e.g. size_of_val would like to work with raw pointers, just have a dummy vtable just enough initialized to say size zero. (In effect, have a dummy ZST that implements all traits by causing UB if used, then point to its vtable.)
I would expect that a ptr::size_of_val(*const dyn Trait) -> usize function would be unsafe, rather than requiring the vtable pointer always be a valid reference (instead of a raw pointer).
This looks like the same mistake that was done with function pointers (being, in practice, &'static references), but until a non-unsafe raw size_of_val is published, there is hope to backtrack.
Let's see if eddyb writes a blog post justifying the current status quo (which feels like a bug to me).
The reason is not having to figure out an intermediate form of "validity" between "proper vtable" and "don't care".
Yes, this could be refined further, but it needs careful design considering custom DST. Until then, the rule is that wide pointer metadata must always be valid, no matter the pointer type. It is easier to relax UB later than to strengthen it.
Personally I'd be in favor of saying that metadata may be invalid for raw pointers, that also avoids having to specify an intermediate form of validity. But clearly that does not reflect the current reality.
Note that this is troublesome with "thin trait objects" where there is no metadata -- a *const Thin most certainly will not have a safe size_of_val.