Workaround for implementing foreign trait on Option<T>

Hello,

I'm a little confused about the rules regarding who is allowed to implement traits. I get that I can implement traits that I define on anything. And that I can implement any public trait on structs or enums that I define. But what about foreign traits on types created from traits that I define?

Specifically, I'm trying to do this:

use core::fmt;
use core::fmt::Display;

trait SomeTrait : Display {}
impl SomeTrait for i32 {}

impl Display for Option<Box<dyn SomeTrait>> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            Some(inner) => inner.fmt(f),
            None => write!(f, "none")
        }
    }
}

fn main() {
    let five_box : Option<Box<dyn SomeTrait>> = Some(Box::new(5));
    println!("{}", five_box);
}

I know about the workaround to wrap the Option<Box<dyn SomeTrait>> in a tuple struct and implement Display on that, but unfortunately that won't work for me because I want this code to also work (and I can't change the make_something function signature):

impl SomeTrait for Box<dyn SomeTrait> {}

fn make_something<T:SomeTrait>() -> Option<T> {
    None
}

fn main() {
    let something_box = make_something::<Box<dyn SomeTrait>>();
    println!("{}", something_box);
}

Is it possible to structure the impls so that this works?

Thank you in advance.

Does this work for you?

A wrapper type (newtype) with a helper function

struct OptionBoxSomeTrait(Option<Box<dyn SomeTrait>>);

impl Display for OptionBoxSomeTrait {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.0 {
            Some(inner) => inner.fmt(f),
            None => write!(f, "none")
        }
    }
}

fn display(x:Option<Box<dyn SomeTrait>>) -> OptionBoxSomeTrait {
    OptionBoxSomeTrait(x)
}

which you then use as

    println!("{}", display(five_box));
    println!("{}", display(something_box));
1 Like

This is only allowed for foreign generic types that have been declared #[fundamental], such as &T, &mut T, and Box<T>. I don't know what the tradeoffs are when declaring a type to be fundamental, but Option seems like a reasonable candidate on the surface.

1 Like

Unfortunately It's not so much that I need the Display trait implemented to get Display behavior as that I need to implement it because several other traits require it.

I have seen this pattern elsewhere (Like in std::path::Path) and I never understood why they would have a method that returned an object that implements Display rather than just implementing Display directly on the Path. So maybe their situation was similar.

Thanks for the reply!

The main trade-off is that right now, Option<T> could get a generic Display implementation without that being a breaking change. With #[fundamental], such an implementation can no longer be introduced later; e.g. when the standard library defines a new trait, they will have to immediately decide whether or not there will be implementations for &T, &mut T, etc. The same thing applies to third-party crates defining traits, too (though they have the additional option of introducing a new major version with breaking changes, which the standard library can’t do).

3 Likes

No, the situation is not similar, I think; AFAICT the reason Path offers a .display() method is because displaying a Path is non-trivial / lossy, and with the explicit method call it’s more obvious that some form of lossy transformation is happening.

I guess, a particularly important reason here is that with a Display implementation, Path would get an (implicitly lossy) .to_string() method via the ToString trait.

3 Likes

Thanks for the replies. I'm still confused about what is happening with Box that allows this to work:

use core::fmt;
use core::fmt::Display;

trait SomeTrait {}

impl Display for Box<dyn SomeTrait> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "box!")
    }
}

But doesn't extend to this:

use core::fmt;
use core::fmt::Display;

trait SomeTrait {}

impl Display for Option<Box<dyn SomeTrait>> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            Some(_inner) => write!(f, "some"),
            None => write!(f, "none")
        }
    }
}

At this point I'm guessing that, whatever the answer is, there isn't anything that can be done. But I don't understand why Box is a type I can implement on and Option isn't.

Thanks again for all your answers.

Box<…> is special in this regard, as is &… or &mut … or Pin<…> (or combinations of them).

This a property (arbitrarily) called “fundamental types”, it’s (quite consisely) described in the reference, here:

https://doc.rust-lang.org/reference/glossary.html#fundamental-type-constructors

Having this property e.g. for &… is quite important if you want to do things like implement IntoIterator for &'a MyCollectionType.

Note that there’s another way in which these types (and also a few more [Arc and Rc] non-fundamental ones) are special, too, w.r.t. traits and methods: They can be used as the type of self parameters.

2 Likes

Oh ok. So the flip side of the guarantee that the language won't add further (global) implementations is that you are allowed to add local implementations (if T is local)

That's the part I didn't understand from what @2e71828 wrote.

I wonder if there is a proposal to extend the [fundamental] property to other types within std / core. (like Option) I searched for one but didn't find anything. It seems that the benefit of being able to implement traits when the struct embeds a local type outweighs the need to keep the option open for global trait impls in the future.

Thanks for the replies!

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.