Constants in dyn traits not allowed - any workaround?

I'm trying to work around a breaking change in winit. Winit used to just a library, but now it has a "framework", a trait that the caller must implement.

I'm stuck maintaining rend3-hp, a fork of non-longer-maintained rend3. I'm trying to keep the same API as rend3 while adapting it to use the new winit. It almost works. One compile error.

Here's the problem. Rend3's "framework" trait starts out like this:

pub trait App<T: 'static = ()> {
    /// The handedness of the coordinate system of the renderer.
    const HANDEDNESS: Handedness;

    fn register_logger(&mut self) {
        #[cfg(target_arch = "wasm32")]
        console_log::init().unwrap();
...

Each application that uses rend3-framework has to provide a value for the const HANDEDNESS. This worked fine until the latest "refactoring" over at winit. Now, winit has its own framework trait that has to be implemented by its user, which in this case is rend3.

pub trait ApplicationHandler {
    /// Emitted when new events arrive from the OS to be processed.

Tying them together looks like this:

/// New Winit framework usage
type AppRef<'a> = &'a mut dyn App;
impl ApplicationHandler<AppRef<'static>> for Rend3ApplicationHandler<'_, AppRef<'_>> {
   ...

The trouble is that "dyn App". If a trait instantiation is dyn, it can't have constants. So the declaration of HANDEDNESS, above, won't work.

Making HANDEDNESS a const parameter to App would work nicely. But that breaks everything that calls rend3, which means I have to change three big programs plus about eight test cases.

So if there's any trick for working around this without changing App, it would help. I don't think there is one, but it's worth asking.

(This is why libraries shouldn't be made into "frameworks". Frameworks want to be in charge. They don't play well with other "frameworks")

1 Like

Does something like this work for your case? Create an object-safe wrapper/proxy trait.

trait NonObjectSafe {
    const VALUE: i32;
    fn some_method(&self) -> i32;
}

trait ObjectSafe {
    fn get_value(&self) -> i32;
    fn some_method(&self) -> i32;
}

impl<T: NonObjectSafe> ObjectSafe for T {
    fn get_value(&self) -> i32 {
        T::VALUE
    }

    fn some_method(&self) -> i32 {
        self.some_method()
    }
}

struct MyStruct;

impl NonObjectSafe for MyStruct {
    const VALUE: i32 = 42;
    
    fn some_method(&self) -> i32 {
        self.get_value() * 2
    }
}

fn use_as_trait_object(obj: &dyn ObjectSafe) {
    println!("Value: {}", obj.get_value());
}

fn main() {
    let obj: &dyn ObjectSafe = &MyStruct;
    use_as_trait_object(obj);
}
1 Like

I'm not familiar with rend3, but this looks somewhat suspect to me-- The type argument to ApplicationHandler is the type that gets put into user events, and I don't see how you can avoid violating the aliasing guarantees of &'static mut in that role. It seems more natural to store the App (or a reference to it) inside the type that's implementing ApplicationHandler

Aside from that, can I ask why you want to use dyn here at all? There's likely to only be one App type in any given program, so monomorphization bloat shouldn't be an issue. My instinct here would be to write an implementation like this, but it's entirely possible that something I don't know about is preventing this approach.

impl<UEData, RendApp> ApplicationHandler<UEData> for Rend3ApplicationHandler<RendApp>
where
    UEData: 'static,
    RendApp: App<UEData>
1 Like

Aside from that, can I ask why you want to use dyn here at all? There's likely to only be one App type in any given program, so monomorphization bloat shouldn't be an issue. My instinct here would be to write an implementation like this, but it's entirely possible that something I don't know about is preventing this approach.

Because App is a trait. So ...

error[E0782]: expected a type, found a trait
   --> rend3-framework/src/lib.rs:336:71
    |
336 | ...r Rend3ApplicationHandler<'_, App<'_>> {
    |                                  ^^^^^^^
    |
help: you can add the `dyn` keyword if you want a trait object
    |
336 | impl ApplicationHandler<App<'static>> for Rend3ApplicationHandler<'_, dyn App<'_>> {
    |                                                                       +++

error[E0782]: expected a type, found a trait
   --> rend3-framework/src/lib.rs:336:25
    |
336 | impl ApplicationHandler<App<'static>> for Rend3ApplicationHandler<'_, A...
    |                         ^^^^^^^^^^^^
    |
help: you can add the `dyn` keyword if you want a trait object
    |
336 | impl ApplicationHandler<dyn App<'static>> for Rend3ApplicationHandler<'_, App<'_>> {

I didn't design this. I'm just trying to make it work without a breaking change for its users (its tests and three applications of my own, at least) as other crates change underneath.

Right. But you should be able to deal with that via a bound on a generic parameter in place of dyn for this case:

impl<'a, T: 'a+App> Something for Rend3ApplicationHandler<'a, T> { ... }
    ^^^^^^^^^^^^^^^

It is likely that this will not be 100% achievable because rend3 includes types defined by these crates in its public API; any breaking change in their usage will therefore necessarily pass through rend3 and break the downstream users. That's not to say that you shouldn't try to minimize the problems, of course, just that you shouldn't expect perfection here.

As a particular example, App::create_window can be overridden by downstream users and is defined to take an argument of type winit::WindowBuilder, which no longer exists in the most recent version of winit.

1 Like

Right. I wasn't able to fix this with zero API changes at the Rend3 API. One API change was necessary - something that was previously a trait-level constant had to be turned into a function, because the virtual function tables for a dyn trait can't dispatch a constant.

Getting the ownership and lifetimes right, while caught between two APIs defined by others, was the usual headache. Here are the diffs for the changes I needed to make. Too many.