Get a runtime error when a trait method is called but not implemented

I would like or call a trait method on a type if that type implements the trait, otherwise I would like to return an error or panic. If there is never an attempt to call the trait method, then there should be no error. I only want an error if there is an attempt to call it, but it doesn't exist.

I want to do something like this:

pub trait Foo {
    fn foo(&self);
}

pub trait Bar {
    fn bar(&self);
}

pub enum Command {
    Foo,
    Bar,
}

pub fn execute_command<T>(t: &T, command: Command) {
    match command {
        Command::Foo => {
            /*
            if T: Foo {
                t.foo();
            } else {
                panic!("Type does not implement Foo");
            }
            */
        }
        Command::Bar => {
            /*
            if T: Bar {
                t.bar();
            } else {
                panic!("Type does not implement Bar");
            }
            */
        }
    }
}

Using the above would look like this:

struct Struct;

impl Foo for Struct {
    fn foo(&self) {
        println!("Foo");
    }
}

fn main() {
    let s = Struct;
    execute_command(&s, Command::Foo); // calm
    execute_command(&s, Command::Bar); // panic
}

I can't seem to figure out how to do this with Rust generics. It's easy with C++ templates but what good does that do me! I thought that maybe I could write a version of execute_command for each combination of Foo and Bar but that would require knowing which function to call (because you can't overload functions). That might depend on negative trait bounds too. It's also a little tedious because the real code has 4 traits so that would be 16 functions.

My first attempt was something like this:

fn as_foo<T: Foo>(t: &T) -> &impl Foo {
    t
}

fn as_foo<T: !Foo>(t: &T) -> &impl Foo {
    panic!("Type does not implement Foo")
}

That's not even close to working because it depends on negative trait bounds and overloading and there's no way to properly use it from execute_command.

I'm stumped. I can't figure it out. Any ideas?

You can provide a default implementation for a trait method that implementors may override:

pub trait Foo {
    fn foo(&self) {
        panic!("Type does not override foo");
    }
}

If you do this you still have to implement Foo for a struct to use it, but it can be an empty implementation:

impl Foo for Struct {}

This is as close as you can get without nightly-only features, I think.

1 Like

It's probably the simplest solution but it's not ideal. With this, implementing Foo loses its meaning because the type may or may not override the default. There are also places where I would like a compile time error if a type doesn't implement a trait.

Here's one approach using min_specialization on nightly. If there's a way to specialize FooWrapper for all Foo implementors, that would be even better. (I'm not familiar enough with min_specialization to know if that's possible, but my initial attempt resulted in compiler errors implying it is not.)

3 Likes

Respectfully, I think you're probably doing something strange.

"Does T implement Foo?" is a question that can always be answered at compile time. There would seem to be no logical reason to defer that question until runtime, since it can always be answered eagerly. That you're mixing runtime and compile-time semantics like this suggests that you're either trying to do the compiler's job for it, or asking the compiler to do your job for you – maybe some of both.

Perhaps a more complete example would help us find another design that would fit your needs better.

3 Likes

I'll provide some more details about what I'm doing.

I'm working on a cross platform library. Foo and Bar are capabilities of a platform. Each platform implements some traits on the Struct (actually called Context). Not all platforms implement all traits. The Command enum encompasses every method on every trait. A Command can be created from a byte array (perhaps received from a network or read from a file) and passed around before being processed by execute_command.

Currently, execute_command is using a small subset of traits that every platform implements. I can't make it support more traits because otherwise it will fail to compile on platforms that don't implement those traits (rather than panic when the capabilities are actually needed).

I guess the most direct way of solving this problem would be to implement execute_command for each platform, but one of the reasons for creating the traits was that I wouldn't need to check the platform and implement things multiple times in a bunch of places. The traits have worked well for that purpose up until now when I want to fail at runtime rather than compile time.

1 Like

You can define a top-level trait with functions like this, for each capability:

trait Context {
    fn get_capability_a(&self)->Option<&dyn CapabilityA> { None }
    /* ... */
}

Each platform struct then overrides the corresponding functions for the capabilities it has:

impl Context for PlatformSpecificStruct {
    fn get_capability_a(&self)->Option<&dyn CapabilityA> { Some(self) }
    /* ... */
}

This can all be wrapped up in a macro, so the platform definitions just need to list the capabilities they’re providing in order to implement the Context trait.

2 Likes

Heh, I just thought of the same thing.

Edit: Unlike the OP, execute_command has a trait bound in this approach, but that seemed reasonable given the use case description. It works on stable.

3 Likes

I like it. It can be applied in more than one place which is nice. I was a little concerned about a runtime cost of the virtual call but I checked Compiler Explorer and it seems like rustc is smart enough to inline it in this case. Awesome!