My advocation for MultiType Return-Position `impl Trait` (MTRPIT) in Rust.
Tags: rust, programming, watercooler
Published: 2023-02-07 00:59:02

## Table of Contents
- 1. [Foreword](#foreword)
- 2. [A Solution](#a-solution)
- 3. [Tradeoffs of `{impl enum T}`](#tradeoffs-of-raw-impl-enum-t-endraw-)
    - 3.1. [The Rules](#the-rules)
        - 3.1.1. [Musts](#musts)
        - 3.1.2. [Cans](#cans)
        - 3.1.3. [Can Nots](#can-nots)
        - 3.1.4. [Revisions](#revisions)
    - 3.2. [Welcome `dyn*`](#welcome-raw-dyn-endraw-)
        - 3.2.1 [`{impl enum T}` vs `dyn* T`](#-raw-impl-enum-t-endraw-vs-raw-dyn-t-endraw-)
- 4. [Current Solutions](#current-solutions)
    - 4.1. [`Box<dyn T>`](#-raw-boxltdyn-tgt-endraw-)
    - 4.2. [Using `enum` Variants as Types](#using-raw-enum-endraw-variants-as-types)
- 5. [Alternatives](#alternatives)
- 6. [Origin](#origin)
- 7. [Further Reading](#further-reading)

&zwnj;<br/>

## Foreword

\[NOTE: `impl enum T` will have `{}` around it (in most cases) to show it's a placeholder type (like `{integer}`).\]  
So, maybe you're a beginner in Rust that wants to return multiple different error `struct`s in a `fn(...) -> Result<T, impl Error>`. Or maybe you're someone advanced working with `impl Iterator`s.
Whatever the case is, you've probably run into the following compiler error... [E0308](https://doc.rust-lang.org/error_codes/E0308.html).
Consider [this code I wrote on the Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=892b21b1d7b7635d2868ef7af9367631):
```rust
trait Test {
  fn test(&self) { println!("Default impl!"); }
}



struct Wibble;
impl Test for Wibble {
  fn test(&self) { println!("Wibble!"); }
}

struct Wobble;
impl Test for Wobble {
  fn test(&self) { println!("Wobble!"); }
}



fn get_test_value(wibble:bool) -> impl Test {
  if wibble { Wibble } else { Wobble }
}

fn main() {
  get_test_value(true).test();
}
```
This code produces the following error:
```
error[E0308]: `if` and `else` have incompatible types
  --> src/main.rs:20:33
   |
20 |     if wibble { Wibble } else { Wobble }
   |                 ------          ^^^^^^ expected struct `Wibble`, found struct `Wobble`
   |                 |
   |                 expected because of this
   |
help: you could change the return type to be a boxed trait object
   |
19 | fn get_test_value(wibble:bool) -> Box<dyn Test> {
   |                                   ~~~~~~~     +
help: if you change the return type to expect trait objects, box the returned expressions
   |
20 |     if wibble { Box::new(Wibble) } else { Box::new(Wobble) }
   |                 +++++++++      +          +++++++++      +
```

Whatfor does this error happen?
It happens because Rust understands returning `impl Test`, but it believes that `impl Test` can/should only refer to one type. This can be an issue because sometimes you'd like to return multiple types that implement `Test`.  

&zwnj;<br/>

## A Solution
So... What would a possible solution be? Other than using `Box<dyn Test>` (which is not very beginner friendly in my opinion).
Well, that's where [RFC #3367](https://github.com/rust-lang/rfcs/pull/3367) comes in. It introduces the idea of static-dispatch in the form of `-> impl Trait` or `-> impl enum Trait` which would act as an alternative to using [`Box<dyn Trait>`](https://doc.rust-lang.org/std/boxed/struct.Box.html).

So, what would this theoretical solution look like in action?
Here's an example of what my code from earlier would look like:
```rust
trait Test {
  fn test(&self) { println!("Default impl!"); }
}



struct Wibble;
impl Test for Wibble {
  fn test(&self) { println!("Wibble!"); }
}

struct Wobble;
impl Test for Wobble {
  fn test(&self) { println!("Wobble!"); }
}



fn get_test_value(wibble:bool) -> impl enum Test {
  if wibble { Wibble } else { Wobble }
}

fn main() {
  get_test_value(true).test();
}
```
Notice the difference?
... Barely? Good. That's the point.
With this you'd be getting \*basically\* the same results as `dyn Test` without the vtable shenanigans.
([Here](https://dev.to/baenencalin/comment/24j67) is what the theoretically generated code would look like.)

How would this work?
When we tell the compiler we would want an `impl enum T`, we would be telling it we want any type that implements the `trait T`, so it knows it must be any of those types.
This would be able to work for the same reason @chayimfriedman2 suggested `enum impl` in [their comment on the RFC](https://github.com/rust-lang/rfcs/pull/3367#issuecomment-1372534383), because (it is like) the compiler is making an `enum` for us.

How is this like the compiler making an example?
Well. Using my code as an example again, [consider what the program may look like if we make in `enum` for all types that implement `Test`](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=edda58fe5eb4c364ff5b634729df4456).
```rust
trait Test {
  fn test(&self) { println!("Default impl!"); }
}

enum Wrapper {
  Wibble(Wibble),
  Wobble(Wobble)
}



struct Wibble;
impl Test for Wibble {
  fn test(&self) { println!("Wibble!"); }
}

struct Wobble;
impl Test for Wobble {
  fn test(&self) { println!("Wobble!"); }
}



fn get_wrapper_value(wibble:bool) -> Wrapper {
  if wibble { Wrapper::Wibble(Wibble) } else { Wrapper::Wobble(Wobble) }
}

fn main() {
  match get_wrapper_value(true) {
    Wrapper::Wibble(w) => w.test(),
    Wrapper::Wobble(w) => w.test()
  }
}
```
The increase in code is negligible here, and we could even make it so `Wrapper` implements `Test` and delegates the job of `.test(&self)` to whatever the given variant would do, but this isn't a good solution because it scales for every type that has implements `Test`. Not to mention that you'd have to (manually) account for blanket implementations.
I think what this kind of static-dispatch brings to the table is invaluable, it's an efficient and typesafe shorthand that would come with virtually zero performance cost.

&zwnj;<br/>

## Tradeoffs of `{impl enum T}`
While having an `{impl T}` or `{impl enum T}` type would be nice, it comes with a few drawbacks.

### The Rules
On the RFC, as @cad97 said [in one of their comments](https://github.com/rust-lang/rfcs/pull/3367#issuecomment-1373050026), the rules for a `Trait` being "enum-dispatch safe" would be very similar to those of [object safety](https://doc.rust-lang.org/reference/items/traits.html#object-safety).

Everything listed below is a compilation of known "rules"/"laws" about how this type should/may work.

#### Musts
- To obey the rules, any super`trait`s must also obey these rules.
- `T` must not be a DST. (I.e. `T: Sized`.)

#### Cans
- For any `{impl enum T}`, `T` can have associated types (with or without generics), associated constants, generics, and non-receiver methods.
- Methods callable from `{impl enum T}` may have have type-parameters.
- `T` may use `&self`, and `&mut self` as method receivers.
- An `{impl enum T}` may be the type of a variable. (The only effective way to obtain a value is via a function that returns `{impl enum T}`.)

#### Can Nots
- No method usable from `T` can return `Self`. (However a value of `{impl enum T}` can be captured.)

#### Revisions
If you have any ideas for changes or additions to these set of rules to better refine or extend them, let me know in the comments and further discussion can be done from there.

### Welcome `dyn*`
What is `dyn*`?
`dyn*` (read as "`dyn`-star") was a feature proposed by [@nikomatsakis](https://github.com/nikomatsakis) in their blogpost [*“dyn\*: can we make dyn sized?”*](https://smallcultfollowing.com/babysteps/blog/2022/03/29/dyn-can-we-make-dyn-sized/).
The proposal basically states that any type `dyn* T`, where `T` is a `trait`, is a sized type that can refer to any value that implements that `trait` **so long** as is pointer-sized or smaller. While this solution ~~is~~ can be what we're looking for, its size-limit can be very... well... limiting. A lot of `struct` types will probably easily surpass that threshold (especially on 32-bit targets).

#### `{impl enum T}` vs `dyn* T`
Ultimately, in my opinion, `{impl enum T}` easily wins, **however** both concepts could be really helpful.
I'm not a programming(language) expert or know how things work behind the scenes, and quite honestly I could use the reading, but it seems like `{impl enum T}` has some advantages over `dyn* T`. The best thing I can think of being the size limitation.
Another reason I can't really get a good comparison of them is because in the article Matsaki **only** discusses use of `dyn* T` as function parameter and not as a return type. (The implications of this type being used as a returntype aren't mentioned either.)

However, as so they can be compared, even if not by me, I will leave some resources under [Further Reading](#further-reading) so you can may judge for yourself.

&zwnj;<br/>

## Current Solutions
Here are some current solutions to get around this.

### `Box<dyn T>`
The, in my opinion unfriendly, `Box<T>` is the compiler's goto recommendation to solve the issue.
Let's see [how the code would look then](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2540fe6e115c50e77944097463156b59):
```rust
trait Test {
  fn test(&self) { println!("Default impl!"); }
}



struct Wibble;
impl Test for Wibble {
  fn test(&self) { println!("Wibble!"); }
}

struct Wobble;
impl Test for Wobble {
  fn test(&self) { println!("Wobble!"); }
}



fn get_test_value(wibble:bool) -> Box<dyn Test> {
  if wibble { Box::new(Wibble) } else { Box::new(Wobble) }
}

fn main() {
  get_test_value(true).test();
}
```
Now it prints "`Wibble!`" with no fuss.
This is dynamic dispatch.

Unfortunately, one drawback to `Box<T>` is that beginners might not understand.
Sure, all you need to know is that it's "like a pointer", but there's some things about it (and its usage) that may be hard for someone new to grasp.

### Using `enum` Variants as Types
This case would look very similar to the one where we wrote `Wrapper`, except `Wibble` and `Wobble` are no longer their own types.
They are reduced to unit variants in `Wrapper`, and wrapper uses a `match` to determine what case to use.
Sometimes this is okay, but its not always a valid solution.

&zwnj;<br/>

## Alternatives
This is where ideas for alternative implementations would go, but I don't have any nor could I find any other really good ideas. <sub>(I'll take suggestions in the comments.)</sub>

\[There is technically one idea, as linked to in the next section; anonymous `enum`s as shown in the pre-RFC *“anonymous enums”*. However it has a flaw I will point out in the next section.\]

&zwnj;<br/>

## Origin

The prototype version of the idea started all the way back on Septemeber 23rd, 2014 with [RFC #294, *“Anonymous sum types”*](https://github.com/rust-lang/rfcs/issues/294), by [@glaebhoerl](https://github.com/glaebhoerl), which had a **completely different** syntax.
To me it looks unrecognizable. Alien. But the syntax reads like this:
```rust
let foo:(~str|int|int) = (!|!|666);
match foo {
  (s|!|!) => println!("{s}"),
  (!|n|!) => println!("{n}"),
  (!|!|m) => println!("{m}")
} 
```
It's not explained what the tilda means or what it does when it is or isn't present.

Eventually this at some point evolved into [*“pre-RFC: anonymous enums”*](https://internals.rust-lang.org/t/pre-rfc-anonymous-enums/5695), which introduces... anonymous `enum`s.
This follows a more understandable syntax:
```rust
let v:Option<i32> = /* ... */

// Type of `result`: enum{i32, &'static str}.
let result = match v {
   Some(v) => v+2,
   None => "Error!"
}

match result {
    #0(v) => println!("{}", v), // `v`: i32
    #1(v) => println!("{}", v)  // `v`: &'static str
}
```
I like this. This is certainly a step in the right direction compared to the last iteration; however, it has two flaws.
One, "What should the ordering of the variants be?".
Two, there is no shared common functionality between `v` in `#0` and `v` in `#1`. With `{impl enum T}` you're promised shared functionality with very similar benefits to this iteration of the idea.

Eventually this `impl Trait` syntax got people excited, especially with the idea of some kind of "anonymous `enum`", and yielded us with [RFC #2414](https://github.com/rust-lang/rfcs/issues/2414), which is pretty similar to what our current #3367 ended up proposing.

&zwnj;<br/>

## Further Reading

### The Rust Programming Language
[*“The Rust Programming Language”*](https://doc.rust-lang.org/book/)

### `dyn T` and `dyn* T`
[*“Using Trait Objects That Allow for Values of Different Types”*](https://doc.rust-lang.org/book/ch17-02-trait-objects.html) of *The Rust Programming Language*.
[*“dyn\*: can we make dyn sized?”*](https://smallcultfollowing.com/babysteps/blog/2022/03/29/dyn-can-we-make-dyn-sized/) by Niko Matsakis
[*“dyn\* doesn't need to be special ”*](https://dev.to/cad97/dyn-doesnt-need-to-be-special-3ldm) by Christopher Durham

### `impl enum T`
[RFC #3367, *“RFC: Multi-Type Return Position Impl Trait (MTRPIT)”*](https://github.com/rust-lang/rfcs/pull/3367)

&zwnj;<br/>

<center>
<b>Thanks for reading!<br/>Cheers!
</center>