Search code examples
genericsrustdowncasttrait-objects

Problem with returning generic type from function


I have a function that searches through a list of trait objects and attempts to find one whose implementor is of a specific type, however the compiler doesent accept the return type.

pub struct GameObject{
    pub modules: Vec<Box<dyn ModuleTrait>>, 
}

impl GameObject {
   pub fn find_module_of_type<T: ModuleTrait>(&self) -> Result<T> {  
        //iterate through the modules
        self.modules
            .iter()
            .for_each(|m| {
                let module = m.as_any().downcast_ref::<T>();

                //see if mosule is of the correct type 
                if match module {
                    Some(T) => true,
                    None => false,
                } {
                    return Ok(module); //this is where the error happens
                }
            });
            
        Err(anyhow!("The specified module could not be found"))
    }
}

pub trait ModuleTrait {
    fn as_any(&self) -> &dyn Any;
}

pub struct Transform{
    pub value: i32
}

impl ModuleTrait for Transform {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

this gives the error

error[E0308]: mismatched types
  --> src\scene\game_object.rs:42:28
   |
42 |                     return Ok(module);
   |                            ^^^^^^^^^^ expected `()`, found `Result<Option<&T>, _>`
   |
   = note: expected unit type `()`
                   found enum `std::result::Result<Option<&T>, _>`
note: return type inferred to be `()` here
  --> src\scene\game_object.rs:42:28
   |
42 |                     return Ok(module);
   |                            ^^^^^^^^^^

For more information about this error, try `rustc --explain E0308`

The error message confuses me even more as it first tells me the unit return type is inferred but at the same time it isnt a unit type.

I tried to unwrap the module but that gave roughly the same error message


Solution

  • Finding an object already has a method on Iterator since you want to change the type as well I used find_map here instead:

    impl GameObject {
        pub fn find_module_of_type<T: ModuleTrait + 'static>(&self) -> Option<&T> {
            self.modules
                .iter()
                .find_map(|m| m.as_any().downcast_ref::<T>())  
        }
    }
    

    Modifications made:

    • for_eachfind_map that's what you want to do after all.
    • ResultOption If your only error is "object matching criteria not found" that's exactly what Option is for. If you want to disambiguate errors further up the call chain you can always turn that Option into a Result with Option::ok_or there.
    • Since you only get a refernce and don't know how to Copy or Clone a T you can't really return a value, only a reference so Option<T>Option<&T>
    • Added a bound of T: 'static since that's a requirement to be able to downcast to it (See the bound on Any).