Return value polymorphism on Box'ed objects?

Let's say I write a module that (simplified for this example here) reads out an EEPROM. Some values there are stored as integer, some as string. In my program code I need some values as integer (to use them for calculation), some of them as string, e.g. for displaying them to the user.

So it would make code easy if I wouldn't have to care for return value type.
Rust supports return value polymorphism by using traits - so why not give it a try?

fn main() {
    let my_mod = MyMod::new();
    let value_u32: u32 = my_mod.my_get().unwrap();
    let value_string: String = my_mod.my_get().unwrap();

    println!("Values: {}, {}", value_u32, value_string);
}

trait SupportsMyGet<T> {
    fn my_get(&self) -> Result<T, String>;
}

struct MyMod {}

impl MyMod {
    pub fn new() -> MyMod {
        MyMod {}
    }
}

impl SupportsMyGet<u32> for MyMod {
    fn my_get(&self) -> Result<u32, String> {
        Ok(42)
    }
}

impl SupportsMyGet<String> for MyMod {
    fn my_get(&self) -> Result<String, String> {
        let r: Result<u32, String> = self.my_get();
        r.map(|v| format!("{}", v))
    }
}

Okay great, this does what I want to do.

Oh okay, I don't know at compile time which implementation to use - this is decided at runtime? How bad. But yeah, there's something in the box for it - let's Box an object that implements my trait and decide at runtime, which object to pass:

fn main() {
    let my_mod = get_mod_to_use().unwrap();
    let value_u32: u32 = my_mod.my_get().unwrap();
    let value_string: String = my_mod.my_get().unwrap();

    println!("Values: {}, {}", value_u32, value_string);
}

fn get_mod_to_use<T>() -> Result<Box<dyn SupportsMyGet<T>>, String> {
    Ok(Box::new(MyMod::new()))
}

(full code in playground)

But that doesn't work out well:

error[E0308]: mismatched types
 --> src/main.rs:4:32
  |
4 |     let value_string: String = my_mod.my_get().unwrap();
  |                       ------   ^^^^^^^^^^^^^^^^^^^^^^^^- help: try using a conversion method: `.to_string()`
  |                       |        |
  |                       |        expected struct `String`, found `u32`
  |                       expected due to this

error[E0277]: the trait bound `MyMod: SupportsMyGet<T>` is not satisfied
  --> src/main.rs:10:8
   |
10 |     Ok(Box::new(MyMod::new()))
   |     -- ^^^^^^^^^^^^^^^^^^^^^^ the trait `SupportsMyGet<T>` is not implemented for `MyMod`
   |     |
   |     required by a bound introduced by this call
   |
   = note: required for the cast to the object type `dyn SupportsMyGet<T>`
help: consider introducing a `where` bound, but there might be an alternative better way to express this requirement
   |
9  | fn get_mod_to_use<T>() -> Result<Box<dyn SupportsMyGet<T>>, String> where MyMod: SupportsMyGet<T> {
   |                                                                     +++++++++++++++++++++++++++++

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile due to 2 previous errors

return value polymorphism is gone... because Rust wants to box a specific object. Leaving out the generic argument also doesn't work as the trait requires it.

So how can this still be done?

Why even use polymorphism here? You can simply use a enum:

enum EEPROMValue {
    Int(u32),
    String(String),
}

Since somewhere in the code you'd decide whether to return an integer or a string, that's where you chose the enum variant.

2 Likes

Yeah but it's not "simply" an enum, this has some side effects as I will have to implement every other trait for my new type too - which basically is already implemented for plain types like u32 and I will have to figure out how to do this manually. Here at least fmt::Display and Div<_> (but there might be more) - see this half done implementation. I tried this for two example codes I wrote, and I didn't see that it was worth the effort - in one example I ended up like this, even having to use derive_more crate as some traits couldn't be derived in standard Rust:

use derive_more::{Display, FromStr};

#[derive(FromStr, Display, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Name(String);

Ideally the compiler would choose it - not me. If I want to display a value, he should simply convert u32 to String. If I want to do calculations he should use u32 and error out if I use a value that's only available as String. Why not use fancy language possibilities like return value polymorphism and let the compiler decide?

If everything you want to return can fulfill all the requirements, you may be able to trait out a solution.

trait FunctionalityIActuallyNeed: ToString {
    fn as_u32(&self) -> u32;
}

impl FunctionalityIActuallyNeed for u32 {
    fn as_u32(&self) -> u32 {
        *self
    }
}

trait SupportsMyGet {
    fn my_get(&self) -> Result<Box<dyn FunctionalityIActuallyNeed>, String>;
}

Your code doesn't demonstrate the need for runtime polymorhphism. In particular, here, you still do apparently know the static type to be returned:

let value_u32: u32 = my_mod.my_get().unwrap();
let value_string: String = my_mod.my_get().unwrap();

What are you actually trying to do? I.e., how are you really trying to use the trait?

@RedDocMD My reply isn't hidden anymore, see above.

Exactly. The code is stripped down to show my problem and not to distract by having too much code.

What I'm actually doing in my live code is that I am writing a command interface to some hardware variants. To stay in my initial example: A command interface to different EEPROMs. All of them implement trait SupportsMyGet<T> for u32 and String (and maybe more in future).
The code to decide on which backend implementation to choose, get_mod_to_use<T>() above, currently is duplicated in my application because of the initial problem I portrayed - I have a get_mod_to_use_u32() and a get_mod_to_use_string() function, which is duplicate code and thus prone to bugs when only one function is changed. Both function bodies are the same, only the return types are different.
When I use my_get() in other places of the code, I always have to make sure to use the correct object type - that would be obsolete if I could have return value polymorphism.

I am guessing you have a background in dynamically typed languages. If not, you can tell me how you'd do this in your preferred statically typed language. For all I know, it is not possible for the compiler to both be efficient and do the thing you have suggested. FIrstly String and u32 is not naturally polymorphic - they are convertible given a radix. Secondly, all implementation that have that sort of polymorphism is forced to utilize heap space (which would be returning via pointer in C) or have a runtime that keeps track of the type for you (Python, Ruby) or both (Java).
What I believe is happening that you have a solution in mind for the problem you have and you are trying to retro-fit Rust to that solution. This is common for new Rust programmers, and is even worse for those with significant prior programming experience.
The solution would be to think in terms of plain enums and structs and traits first and then resort to dynamic polymorphism.

1 Like

Well, you can return a Box<dyn Trait> which is made from either a string or an integer as specified by a type parameter. For instance, you can do this:

use std::fmt::Display;

fn make_displayable<T: Display + Default + 'static>() -> Box<dyn Display> {
    Box::new(T::default())
}

fn print_dyn(x: &dyn Display) {
    println!("x = `{}`", x);
}

fn main() {
    let x = make_displayable::<u32>();
    let y = make_displayable::<String>();

    print_dyn(&*x);
    print_dyn(&*y);
}

Is this what you are looking for?

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.