Returning a Trait implementation using different implementations

Viewed 80

The following implementation seems quite simple. for some reason I don't get what the problem is. Compiler doesn't compile.

The main problem here is that I cannot change the declaration of the function test(bool) since it's out of a framework.

/// Implemented in a 3rd party framework:
trait Share {
    fn dosomething();
}

impl Share for String {
    fn dosomething() {
        todo!()
    }
}

/// My part of the implementation:
// this function `test` will be handed over (as a function) to a framework method, which I can't change it's return type.

fn test(data: bool) -> Result<impl Share, String> {
    return if data {
        Ok(Data {})
    } else {
        Ok("a string".to_string())
    }
}



struct Data {

}

impl Share for Data {
    fn dosomething() {
        todo!()
    }
}
   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/lib.rs:5:12
  |
5 |         Ok("a string".to_string())
  |            ^^^^^^^^^^^^^^^^^^^^^^ expected struct `Data`, found struct `String`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error
2 Answers

The problem is that both branches of the if-else expression must evaluate to a value of the same type. However, in your code:

fn test(data: bool) -> Result<impl Share, String> {
    return if data {
        Ok(Data {}) // : Result<Data, String>
    } else {
        Ok("a string".to_string()) // : Result<String, String>
    }
}

The first branch evaluates to Ok(Data {}), and therefore its type would be inferred as Result<Data, String>, whereas the second branch evaluates to Ok("a string".to_string()) and it would be inferred as Result<String, String>. Since these types are different, it results in a compile-time error.


If Share were an object-safe trait, e.g.:

trait Share {
    fn dosomething(&self);
}

Then, since the values Data{} and "a string".to_string() implement Share, you could turn them into trait objects by placing them into a Box<dyn Share>. This way, you would still achieve a (run-timeX) type erasure by relying on dynamic polymorphism:

fn test(data: bool) -> Result<Box<dyn Share>, String> {
    return if data {
        Ok(Box::new(Data{}))
    } else {
        Ok(Box::new("a string".to_string()))
    }
}

Note that here both branch values are of the same type: Result<Box<dyn Share>, String> because the types Box<Share> and Box<String> are erased at run-time to Box<dyn Share>.


XYou were aiming at a compile-time type erasure with your initial impl Share.

If you really need to provide different types for the Ok variant of the result, all of them implementing Share, then you have to switch to dynamic polymorphism.

fn test(data: bool) -> Result<Box<dyn Share>, String> {
    if data {
        Ok(Box::new(Data {}))
    } else {
        Ok(Box::new("a string".to_string()))
    }
}

trait Share {
    fn dosomething(&self);
}

impl Share for String {
    fn dosomething(&self) {
        println!("something on String");
    }
}

struct Data {}

impl Share for Data {
    fn dosomething(&self) {
        println!("something on Data");
    }
}

fn main() {
    if let Ok(s) = test(true) {
        s.dosomething();
    }
    if let Ok(s) = test(false) {
        s.dosomething();
    }
}

This implies heap allocation (Box) because the value is produced inside the function (you cannot return a reference to a local).

However, if you only want to provide a limited set of types implementing Share then, you can return an enum instead of impl Share.

enum VariousTypes {
    D(Data),
    S(String),
}

impl Share for VariousTypes {
    fn dosomething(&self) {
        match self {
            Self::D(d) => d.dosomething(),
            Self::S(s) => s.dosomething(),
        }
    }
}

fn test(data: bool) -> Result<VariousTypes, String> {
    if data {
        Ok(VariousTypes::D(Data {}))
    } else {
        Ok(VariousTypes::S("a string".to_string()))
    }
}

trait Share {
    fn dosomething(&self);
}

impl Share for String {
    fn dosomething(&self) {
        println!("something on String");
    }
}

struct Data {}

impl Share for Data {
    fn dosomething(&self) {
        println!("something on Data");
    }
}

fn main() {
    if let Ok(s) = test(true) {
        s.dosomething();
    }
    if let Ok(s) = test(false) {
        s.dosomething();
    }
}
Related