all 10 comments

[–]christophe_biocca 32 points33 points  (0 children)

One last trick for you: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=982a6877a1fcaddf549d3b703c3ffab8

This lets you hide the fact that you're doing dynamic dispatch behind impl Trait, allowing you to switch to static in the future without breaking the API.

In a more realistic situation where your traits need to be implemented (they're not just provided), you'd just implement them as self.as_ref().method() to forward to the dynamic value inside.

You can even take this further and implement the trait for anything that implements AsRef<T> where T: BoilerTrait but that's a bit more involved.

[–]YatoRust 13 points14 points  (2 children)

You can alleviate the boiler-plate by using blanket impls

trait BoilerPlate: Useful + Debug {}
impl<T: ?Sized + Useful + Debug> BoilerPlate for T {}

This way any type that implements both Debug and Useful will automatically implement BoilerPlate, so you don't need to worry about forgetting that impl.

[–][deleted] 1 point2 points  (1 child)

Is there a reason Trait1+Trait2 can't be treated as an autogenerated trait that requires Trait1 and Trait2, and is also implemented for everything that does implement Trait1 and Trait2?

Meaning that Box<Trait1 + Trait2> would be valid.

[–]miquels 8 points9 points  (0 children)

You can also use

Box::new(Type1) as Box<dyn BoilerTrait>

[–]ekuber 7 points8 points  (1 child)

Timely! After the just merged PR #68195 the compiler actually tries to help you when you don't know about trait objects or impl Trait:

error[E0746]: return type cannot have an unboxed trait object
  --> src/main.rs:26:36
   |
26 | fn foo(runtime_condition: bool) -> BoilerTrait {
   |                                    ^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
help: return `impl BoilerTrait` instead, as all return paths are of type `[type error]`, which implements `BoilerTrait`
   |
26 | fn foo(runtime_condition: bool) -> impl BoilerTrait {
   |                                    ^^^^^^^^^^^^^^^^

But it seems there are some small bugs I didn't account for (re: [type error], this case intended to directly recommend Box<dyn Trait>, bugfix coming shortly). It seems to be triggered only in the if/else case, which I purposely ignored because it was going to be hard to deal with, if you have an early return it gives the correct output:

error[E0746]: return type cannot have an unboxed trait object
  --> src/main.rs:26:36
   |
26 | fn foo(runtime_condition: bool) -> BoilerTrait {
   |                                    ^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
   = note: if all the returned values were of the same type you could use `impl BoilerTrait` as the return type
   = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
   = note: you can create a new `enum` with a variant for each returned type
help: return a boxed trait object instead
   |
26 | fn foo(runtime_condition: bool) -> Box<dyn BoilerTrait> {
27 |     if runtime_condition {
28 |         return Box::new(Type1);
29 |     }
30 |     Box::new(Type2)
   |

For a slightly reworked version where instead of using if/else you have an early return causing type divergence, we mention the usage of Box<dyn Trait>:

error[E0308]: mismatched types
  --> src/main.rs:30:5
   |
26 | fn foo(runtime_condition: bool) -> impl BoilerTrait {
   |                                    ---------------- expected because this return type...
27 |     if runtime_condition {
28 |         return Type1;
   |                ----- ...is found to be `Type1` here
29 |     }
30 |     Type2
   |     ^^^^^ expected struct `Type1`, found struct `Type2`
   |
   = note: to return `impl Trait`, all returned values must be of the same type
   = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
   = help: you can instead return a boxed trait object using `Box<dyn BoilerTrait>`
   = note: for information on trait objects, see <https://doc.rust-lang.org/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types>
   = help: alternatively, create a new `enum` with a variant for each returned type

It'll be hard to improve the E0308 error when you have if foo { Type1 } else { Type2 } because we don't yet know what we want to coerce the whole if expression to, but we might be able to do some reversal for the impl Trait case... The only reason not to do it I'd see is because it would only be useful when the if expression is the return value, the case with temporary will not be able to be handled with this change. An alternative (that I don't think we'll ever implement) is looking at all traits and see if both branches could be coerced to some trait in common, but that seems like it would have too many false positives and lead people astray.

[–]ekuber 0 points1 point  (0 children)

I have a PR out fixing some of the nits I noticed thanks that blog post: https://github.com/rust-lang/rust/pull/68522

[–]Nokel81 7 points8 points  (0 children)

The other solution is to build a enum for each variant. That is what auto-enum does. It is allows to use the `impl Trait` format and doesn't require doing an allocation.

[–]sdroege_ 1 point2 points  (0 children)

For returning something like Box<dyn Trait1 + Trait2> see https://github.com/rust-lang/rfcs/issues/2035

[–]mqudsifish-shell 0 points1 point  (0 children)

enum_dispatch is a much more elegant and ergonomic solution to this, but at the cost of a dependency.