Possible APIs missing from ptr_metadata

Hi Rustaceans,

I'm sorry if this isn't the appropriate forum for a question on the design of an unstable feature.

I was trying out the ptr_metadata feature, and noticed that this code doesn't compile:

use std::{any::Any, ptr::Pointee};
fn unsized_metadata<T: ?Sized + Any>(
    x: &T,
) -> <dyn Any as Pointee>::Metadata {
    std::ptr::metadata::<dyn Any>(x)
}

This has the following error:

error[E0277]: the size for values of type `T` cannot be known at compilation time
...
note: required for the cast from `T` to the object type `dyn Any`

Seeing as std::ptr::metadata(..) takes ?Sized and there exists a metadata type DynMetadata<Dyn: ?Sized>, it seems like there should be a way to get the DynMetadata from an unsized reference. I'm not sure of any other way to get an instance of that metadata type.

Am I missing something?

This is a more general problem. If you have a trait

trait Foo: Any {}

then this is not enough to convert from &dyn Foo to &dyn Any because the vtable for dyn Foo doesn't have enough information to provide such a conversion.

Your code fails because if T = dyn Foo, then it would need to make a conversion like the above, which is not possible.

2 Likes

Hmm, I think that specifically is possible, but I may be misinterpreting what you mean.

pub trait AsAny: Any {
    fn as_any(&self) -> &dyn Any;
}
impl<T: Any> AsAny for T {
    #[inline(always)]
    fn as_any(&self) -> &dyn Any {
        self
    }
}

The built-in cast from &T to &dyn Trait has two requirements:

  1. T must implement Trait
  2. T must be Sized

If we compare this to your impl block:

  1. Since you specify that T: Any, it does indeed implement the Any.
  2. Since you do not specify T: ?Sized, the type is indeed required to be Sized.

Therefore, the cast from &T to &dyn Any inside the as_any method is allowed.

To be clear, just because you can implement a method that performs the conversion, this does not mean that the compiler's built-in cast will have the same ability. Every single combination of dyn A to dyn B conversions would require a separate entry in a vtable somewhere, and Rust does not want to add all possible conversions to your executable. They cannot be dead-code eliminated.

4 Likes

Thank you for explaining!

So back to your original reply, I’m going to interpret your trait Foo: Any {} example not as “it can’t be done”, but as “it won’t work without adding something to the vtable”.

However the cast in my original question is explicitly ?Sized - so does not meet the first of the cast requirements you outlined. So can’t be cast that way.

So I suppose my original question becomes: How do I get DynMetadata<dyn MyTrait> for an unsized concrete or generic type? Even if I have to add an as_trait style method somewhere, there doesn’t seem to be a way to do this. This seems like a hole in the current ptr_metadata API.

That's trait_upcasting. But trait upcasting still isn't enough for the code in your OP, as

  • for example a str is Any (and not Sized), but has no vtable (and can't be coerced to dyn Any)
  • you need to know you're a dyn Something for trait upcasting
    • with Any as a supertrait of Something
    • which in turn requires all implementers to be 'static, too onerous for many traits

It's a hole in Rust more generally, e.g. a &str can't become a &dyn Anything, and as another example if I want this to be object safe:

trait Trait {
    fn one_method_of_many<T: Bound>(&self);
}

Then I have to do

trait Trait {
    fn one_method_of_many<T: Bound>(&self) where Self: Sized;
}

And now if I implement the trait for str, say, I can't use that method.

Things would be improved if there was a NotDyn trait so that the hack of using Sized for that meaning could be removed (we'd need Sized: NotDyn). Arguably that's something like

!Pointee<Metadata = DynMetadata<Self>>

I'm not sure exactly what your goal is though. Do you want "DynMetadata<T> of this thing you passed", or do you want "DynMetadata<dyn Any> of this thing you passed after it gets coerced to dyn Any"? The latter is what your OP seems to attempt.

If that's accurate, you need a trait bound saying the coercion from &T to &dyn Any is possible. That's another unstable feature, coerce_unsized.

fn unsized_metadata<'a, T>(x: &'a T) -> DynMetadata<dyn Any>
where
    T: ?Sized,
    &'a T: CoerceUnsized<&'a dyn Any>,
{
    core::ptr::metadata(x as &dyn Any)
}

(If it was the former, that's just the below.)

fn unsized_metadata<'a, T>(x: &'a T) -> DynMetadata<T>
where
    T: ?Sized + Pointee<Metadata = DynMetadata<T>>,
{
    core::ptr::metadata(x)
}
1 Like

Awesome, thank you very much @quinedot! It was CoerceUnsized that I was missing. I'd come across it before, but I couldn't work out from what I read what it was supposed to do.

So I believe in summary:

  • The ptr_metadata wasn't missing an API, I just couldn't prepare the right reference
  • To obtain my reference for the metadata API I need another feature (CoerceUnsized)

Thanks!

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.