2

I have these two types:

pub struct A;

pub struct B;

The following trait:

pub trait MyTrait {}

and a free function:

fn do_something(mut element: impl MyTrait) { ... }

I want to, based on the real type behind the impl, do something different depending the type of the element that is coming in the current function call.

// pseudo-snippet

match element {
    A => do something with A,
    B => do something with B
}

Is possible in Rust, determine the type behind an impl statement? Or, at least, make some decision making workflow based (if-else branches, patter matching...) on that impl?

Alex Vergara
  • 1,766
  • 1
  • 10
  • 29

2 Answers2

3

The way to do this is to add a method to MyTrait, which does the two different things you want, or which returns an enum that you can match.

pub trait MyTrait {
    fn do_something(self);
}

There is no mechanism to directly branch on what a concrete type is in the way you mean. Technically you could achieve it with std::any::TypeId, but you shouldn't — because you're defeating static checking, making it possible for an error in your program (not handling the right set of types) to be delayed until run time. Instead, by putting the functionality into MyTrait, your program has the property that if some type implements MyTrait, there is necessarily a definition of what do_something should do with that trait.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
2

There is no syntax for match on types. The only way to do any kind of "downcasting" is via the Any trait. You can use it with an impl MyTrait if it also constrained to be 'static:

use std::any::Any;

pub struct A;
pub struct B;
pub struct C;

pub trait MyTrait {}

impl MyTrait for A {}
impl MyTrait for B {}
impl MyTrait for C {}

fn do_something(mut element: impl MyTrait + 'static) {
    let element = &mut element as &mut dyn Any;
    
    if let Some(a) = element.downcast_mut::<A>() {
        // do something with `a`
        println!("is A");
    } else if let Some(b) = element.downcast_mut::<B>() {
        // do something with `b`
        println!("is B");
    } else {
        // do something with `element`
        println!("is unknown");
    }
}

fn main() {
    do_something(A);
    do_something(B);
    do_something(C);
}
is A
is B
is unknown

The two caveats with this code are 1) the 'static constraint, meaning you can't use this with traits or implementations with non-static generic lifetimes; and 2) you can't get an owned version of A or B (the type of a is &mut A in the above code).


This type of code pattern is very un-idiomatic. If you want to have trait instances exhibit different behavior based on their implementations, you should have an associated method for it:

pub trait MyTrait {
    fn do_something(self) 
}

Or if you want to implement behavior on a closed-set of types with shared behavior, you should instead use an enum:

enum MyEnum {
    A(A),
    B(B),
}

fn do_something(element: MyEnum) {
    match element {
        MyEnum::A(a) => { println!("is A"); },
        MyEnum::B(b) => { println!("is B"); },
    }
}

With either of these methods, you don't have the same caveats of trying to circumvent the polymorphic trait system.

See also (none address the impl Trait case, but they pretty much all say the same thing):

kmdreko
  • 42,554
  • 6
  • 57
  • 106