Let's say you have a trait that has a method that lets you get it as a different trait:
trait Bar { }
trait Foo: Bar {
fn as_bar(&self) -> &dyn Bar;
}
All implementations of this method will be the same; i.e.
fn as_bar(&self) -> &dyn Bar { self }
Therefore, it'd be nice to offload this needless repetition to the trait itself by offering a default implementation, i.e. the exact same as the above:
fn as_bar(&self) -> &dyn Bar { self }
If you try this, however, the compiler will yell at you:
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:4:36
|
4 | fn as_bar(&self) -> &dyn Bar { self }
| ^^^^ doesn't have a size known at compile-time
|
= note: required for the cast to the object type `dyn Bar`
help: consider further restricting `Self`
|
4 | fn as_bar(&self) -> &dyn Bar where Self: Sized { self }
| ^^^^^^^^^^^^^^^^^
If you try to follow the compiler's advice by putting where Self: Sized on it, however, you can no longer use as_bar() on dyn Foos, missing the point entirely. I understand why it can't assume Self: Sized because you can implement traits for types that are ?Sized, but it's really frustrating to have to strew
fn as_bar(&self) -> &dyn Bar { self }
on every type you impl the trait for, just because default impls can't make certain assumptions, and "fixing" those assumpetions with Self: Sized breaks something else about the impl instead.
Am I missing some crucial detail, or is this just an unsolvable problem?
AFAIK, this is currently unsolvable. Default impls with extra restrictions are part of the unstable specialization feature (which is – by the way – in my opinion quite unfortunate because this way the feature is needlessly blocked on all the problems of specialization like its soundness issues):
#![feature(specialization)]
trait Bar {}
trait Foo: Bar {
fn as_bar(&self) -> &dyn Bar;
fn more_methods(&self);
}
default impl<T: Foo> Foo for T { // <- T has an implicit T: Sized bound
fn as_bar(&self) -> &dyn Bar {
self
}
}
struct SomeStruct {}
impl Bar for SomeStruct {}
impl Foo for SomeStruct {
fn more_methods(&self) {
// …
}
}
Okay, maybe it’s not entirely unsolvable, yet IMO it would be way nicer if Rust just got a feature that allowed proper default implementations with additional bounds. An additional trait will do the job, I guess. It could even become a supertrait of Bar itself, so that other traits like Foo that extend Bar will all get the as_bar method for free.
trait Bar: AsBar {}
trait AsBar {
fn as_bar(&self) -> &dyn Bar;
}
impl<T: Bar> AsBar for T { // <- T has an implicit T: Sized bound
fn as_bar(&self) -> &dyn Bar {
self
}
}
trait Foo: Bar {
fn more_methods(&self);
}
struct SomeStruct {}
impl Bar for SomeStruct {}
impl Foo for SomeStruct {
fn more_methods(&self) {
// …
}
}
fn foo(x: &dyn Foo) {
// it works!
let y: &dyn Bar = x.as_bar();
}
Fortunately, min_specializationcan (verbosely) handle similar cases, so there’s a chance this will be possible on stable before the heat death of the universe.
Note that's more-or-less what I originally proposed in the linked IRLO thread, but the only response I got was "specialization can already do that." It's an unsatisfying answer because the specialization feature seems doomed to nightly purgatory for the forseeable future; nobody seems interested in pursuing this separately.