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):
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.
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.
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).
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.
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.
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.
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.