So, about enum-impl-Trait. Suppose we have
trait Heavy {
fn new() -> Self;
fn check(&self);
fn collide(&self, other: &Self);
fn sink(self: Box<Self>);
}
struct Ship([u8; 500]);
struct Iceberg([u64; 90000]);
impl Heavy for Ship { ... }
impl Heavy for Iceberg { ... }
fn make_a_heavy(thing: &str) -> enum impl Heavy { // <---
match thing {
"ship" => Ship::new(),
"iceberg" => Iceberg::new(),
_ => panic!("invalid input"),
}
}
let ship = Box::new(make_a_heavy("ship"));
ship.check();
ship.collide(&make_a_heavy("iceberg"));
ship.sink();
Now the problem is, how do we properly implement that anonymous type enum impl Heavy? Consider my previous suggestion,
enum __Anonymous$Heavy$make_a_heavy {
Branch1(Ship),
Branch2(Iceberg),
}
impl Heavy for __Anonymous$Heavy$make_a_heavy {
fn new() -> Self {
unimplemented!()
}
fn check(&self) {
match *self {
Self::Branch1(ref a) => a.check(),
Self::Branch2(ref a) => a.check(),
}
}
fn sink(self: Box<Self>) { // ???????
match self {
box Self::Branch1(a) => (box a).transfer(),
box Self::Branch2(a) => (box a).transfer(),
}
}
fn collide(&self, other: &Self) { // ????????????????
match (*self, *other) {
(Self::Branch1(ref a), Self::Branch1(ref b)) => a.collide(b),
(Self::Branch2(ref a), Self::Branch2(ref b)) => a.collide(b),
_ => panic!("oh no I did not expect that"),
}
}
}
-
new()
-
It should not be possible call associated methods without knowing the type itself, so it should be fine to implement is as unreachable.
-
check(&self)
-
These just forward to their concrete type’s respective methods. Similar for methods that take &mut self and self.
-
sink(self: Box<Self>)
-
Now this one is interesting. Unlike the references and Self itself, you cannot zero-cost transform a Box<A> into a Box<B>. You have to allocate a new box, and perform an possibly expensive memcpy.
Furthermore, here we used a box-pattern here. If we do implement https://github.com/rust-lang/rust/issues/44874, how are we going to deconstruct the Self out of a Mutex<Self>? Rc<Self>? my_crate::CrazySmartPointer<Self>?
-
collide(&self, other: &Self)
-
This one is plain impossible to forward. The definition of the trait only prepared how to collide a Ship with a Ship and an Iceberg with an Iceberg, so when we bring two enum impl Heavy of different types together, we could only respond by panicking. This is very undesirable for compiler-generated code!
If we implement enum-impl-Trait using enums, it means we have to also introduce a concept of “enum-impl-Trait-safe methods” (just like object-safe methods), which:
- only take
self, &self or &mut self as the first argument, and
- does not refer to
Self in any other arguments, and
- returns either
Self, or a type that does not involve Self.
Or do we have other ideas to get around these restrictions?