Why not allow constants in trait objects?

i don't see any reason why the following code can't compile

trait MaybeSigned {
    const IS_SIGNED: bool;
}

impl MaybeSigned for usize {
    const IS_SIGNED: bool = false;
}

fn get_dyn() -> Box<dyn MaybeSigned> {
    Box::new(32 as usize)
}

the generated vtable would look something like this:

struct MaybeSigned__Vtable {
    IS_SIGNED: bool,
}

i don't see a real reason vtable fields can't have types other than function pointers with a self reciever, is there something i'm missing?

1 Like

You could make it available as a value, but it couldn't be a constant value, nor accessible through on the type. For example, consider the following valid code:

trait MaybeSigned {
    const IS_SIGNED: bool;
}

fn foo<T: ?Sized + MaybeSigned>() -> bool {
    T::IS_SIGNED 
}

(Less trivial but equally troublesome cases would be where foo takes two or more &Ts instead of zero, and does an actual const {...} computation using the associated constant — then there is no single value that matches.)

There is no value for foo::<dyn MaybeSigned>() to return. Yet according to the bounds, dyn MaybeSigned meets the requirements of T. So, we'd either need some new kind of trait bound, or dyn MaybeSigned would exist but not actually meet the trait bound (which is somewhat useful, but weird and would itself need new rules).

7 Likes

What do you expect it to support on a dyn Trait that you can't do with fn is_signed(&self) -> bool?

hmm, so it seems like it's mainly a syntactic issue, not a semantic one?

perhaps we could implement a dyn const construct, in generic contexts, these constants would be accessed like fields: x.IS_SIGNED.

for all instantiations that do not use dyn, this would be exactly equivalent to an associated constant lookup. however, in the case of dyn Trait, it would simply look up the value in the vtable.

this should be a bit more performant than using methods that return fixed values, since i is just an offset read instead of a function call.

2 Likes

This could work if associated consts became named parameters to the dyn Trait ala associated types. In the meanwhile you can approximate it with a type-per-const-value and a shadow trait.

I don't think "it's in the vtable" is enough with consts in the type system. A &dyn Trait value can change between vtables without changing type.

We already have these.

That is certainly interesting. I suppose what's occurring is that essentially the compiler provides something equivalent to

impl Weird for dyn Weird + '_
where
    BoundGuard<Self>: BoundTrait,
{
}

and permits dyn Weird to be used since Weird is allegedly dyn-safe, but the additional bound means that dyn Weird: Weird never holds. And this behaves differently from where Self: SuperTrait because bounds directly on Self are treated differently due to being automatically elaborated.

I wonder if you could abuse this and write a bound on TIdentity<Self>::Output: SuperTrait to get an effective supertrait bound but without supertrait bound elaboration or upcasting.

We already have that as an unstable feature — it just doesn't enable use of the trait as a dyn type. Demonstration:

#![feature(associated_const_equality)]
trait MaybeSigned {
    const IS_SIGNED: bool;
}
fn foo<const S: bool, T: ?Sized + MaybeSigned<IS_SIGNED = { S }>>() {}
fn main() {
    foo::<false, dyn MaybeSigned<IS_SIGNED = false>>();
}

This could, I think, be added to the language without any semantic complications.

Yes, but those currently don't allow access to any part of the actual vtable. They simply don't implement the trait at all.

1 Like

can you elaborate on that? i don't see how that's possible. you could maybe downcast (or upcast it?) it, but that would change the type.

You could call this a nit, but parts of the vtable are accessed. (The drop pointer and the supertrait methods, in the playground.) ((Probably also the size and maybe alignment too, if I had boxed it.))

(Edit: eh, you need to box it for the drop to be accessed via vtable too, but you get the point.)

As a slight aside, I wish there was a convenient way to provide a default method body that assumes Self: Sized but still allows the method to be dispatched via vtable. That would allow a solution like this:

#![feature(generic_const_items)]

trait MaybeSigned {
    const IS_SIGNED: bool where Self: Sized;

    #[default_impl_assumes_sized] // or something similar…
    fn is_signed(&self)->bool { Self::IS_SIGNED }
}

i think this would work fine with dyn const, since the semantics would be identical to that of a struct field. it couldn't actually be used in constant expressions in a generic context.

Rust doesn't have "trait fields" either, though it has been proposed. But even if it did, you can use an associated const without having a value of the type around.

trait MaybeSigned {
    const IS_SIGNED: bool;
}

fn is_signed<T: ?Sized + MaybeSigned>() -> bool {
    T::IS_SIGNED
}
// Or if you prefer
impl MaybeSigned for dyn MaybeSigned + '_ {
    const IS_SIGNED: bool = ???;
}

The thing I'm proposing, dyn const, would restrict that, they would only be accessible from a value that implements that trait, not from the type itself.

1 Like

Sure, I am still talking about dyn Trait and not something new to the language.

Does this mean your OP is answered?

1 Like

I suppose so, yes.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.