---
# System prepended metadata

title: Expanding dyn-capability

---

---
title: "Meetup 2024: Expanding dyn-capability"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2024-09-10
url: https://hackmd.io/l_EJFwlsT8KojFQIY-HoOQ
---

# Expanding dyn-capability

* What makes traits not dyn-capable? How can we fix it so that they are?
    * E.g. `dyn Iterator<Item=ConcreteType>` is (sometimes) unfortunate
    * If all I really want is `dyn Iterator<Item=dyn ItemBound>` (in effect; or maybe `dyn Iterator<dyn* ItemBound>`, not sure...), can we make that easier for people to reach for?
    * also, trait methods with generics will immediately be non dyn-capable. Can we expand things (namely for `fn method(&self, impl Bound)`) so that *that* is at least supported via some dyn magic.

Specific problematic cases:

Case 1. (returning) associated types from method that you might want to call from a dyn trait

Case 2. generic method (aka a method with a generic type binding directly on it), particularly closures

Case 1 has the "normal" workaround of specifying concrete type in `dyn Iterator<Item=ConcreteType>`.

> ```rust
> fn print(iterator: &mut impl Iterator<Item: Display>) {}
> // means:
> fn print<I: Iterator>(iterator: &mut I)
> where
>     I::Item: Display
> {}
> ```

```rust
// no need to statically know `Item`
fn print(iterator: &mut dyn Iterator<Item: Display>) {
    for item in iterator {
        println!("{item}");
    }
}
```


```rust
// no need to statically know `Item`
fn print(iterator: dyn* Iterator<Item = dyn* Display>) {
    // dyn* Iterator<Item: Display> ==> dyn* Iterator<Item = dyn* Display>
    // impl Iterator<Item: Display> ==> impl Iterator<Item = impl Display>
    for item in iterator {
        println!("{item}");
    }
}
```


FWIW, [this compiles today](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=34291cc1808087b8b26b57891f61cb8a):
```rust
fn print(iterator: impl Iterator<Item: Display>) {}
```

Niko's breakdown =)

* "Erased trait" pattern from serde + friends
* "Partially dyn" traits
* "Partially known" associated types whose values you don't care about
* Bounded associated types with dyn values

Note: following rustc's suggestion to use `&mut dyn Iterator<Item = impl Display>` "works", except that it injects an implicit type parameter and causes you to generate multiple copies of the code during monomorphization of that type parameter.

We could write `dyn Iterator<Item: dyn Display>`, which we would kinda want to mean "translate the item's concrete type to be a dyn".


---

```rust
trait Foo {
    fn fmt(&self);
}
```

* Could have "partial dyn" trait support, where you can turn (a reference to) any value implementing (any) Trait into an dyn Trait, and merely say that `dyn Trait` isn't allowed to invoke all of the non dyn-compatible methods. **But** consider the below, where one attempts to pass a `&mut dyn Trait` to a function taking an `&mut impl Trait`:

```rust
fn foo(f: &mut dyn Trait) {
    bar(f);  // still has the idea of "dyn compatibility" -- does `dyn Trait: Trait`
}

fn bar<F: ?Sized + Trait>(f: &mut F) {
    
}
```

Supporting the above is then problematic, because `bar` has no limits on what methods of Trait it might invoke.


----

(Side note (?):) there is a gotcha regarding how dispatch ends up resolving method calls: you may end up in the default method (analogous to a base class method in C++) when you might have expected dispatch to occur; see below:


```rust
trait Iterator {
    type Item;
    
    fn next(&mut self) -> Option<Self::Item>;
    
    fn map<F: FnMut>(self, f: F) -> Map<Self, F> where Self: Sized;
}

impl<I> Iterator for &mut I
where
    I: ?Sized + Iterator,
{}
```

* `<I as Iterator>::map` -- ok, "optimized"
* `<dyn Iterator as Iterator>::map` -- inaccessible
* `<&mut dyn Iterator<Item = X> as Iterator>::map` -- the default but going through vtable for each `next` call

An example of the current workaround without `final`:

```rust
pub trait AsyncGen {
    type Item;
    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>;
}

pub trait AsyncGenNext: AsyncGen {
    type Next<'s>: Future<Output = Option<Self::Item>> where Self: 's;
    fn next(self: Pin<&mut Self>) -> Self::Next<'_>;
}

impl<T: AsyncGen + ?Sized> AsyncGenNext for T {
    type Next<'s> = impl Future<Output = Option<Self::Item>> where Self: 's;
    fn next(mut self: Pin<&mut Self>) -> Self::Next<'_> {
        core::future::poll_fn(move |cx| self.as_mut().poll_next(cx))
    }
}
```

Here's the impl for `&mut impl Iterator`, which is what `&mut dyn Iterator` hits:

<https://doc.rust-lang.org/1.81.0/src/core/iter/traits/iterator.rs.html#4106>

---

How can we just make it possible to express OO patterns in Rust with dyn?

```rust
dyn trait Foo {}
```

Niko: Wanting to subset comes up fairly often in practice.

Tyler: Works; sometimes you want to let the caller choose if they want dyn-dispatch which wouldn't work for traits that have non-dyn-compatible method

Scott: You could subset with something like `impl dyn Foo`

---

What makes dyn so hard to use? The fact that it's not sized is a big one.

(and that was motivation for [`dyn*`...](https://smallcultfollowing.com/babysteps/blog/2022/03/29/dyn-can-we-make-dyn-sized/))


---

Regarding case 2 above, we can special case `impl Trait` (as opposed to arbirary generic methods) like so:

```rust
trait GiveFuture {
    fn method(&self, f: impl Future<Output = u32>);
}

// can be emulated roughly like so
impl GiveFuture for dyn GiveFuture {
    fn method(&self, mut f: impl Future<Output = u32>) {
        let f: &mut dyn Future<Output = u32> = &mut f;
        <$UnderlyingType as GiveFuture>::method::<&mut dyn Future<Output = u32>>(self, f) // vtable dispatch
    }
}
// relies on
// * impl Future for &mut dyn Future
//    * `impl Future: Sized` 
// * if `Future: 'static` bound, this wouldn't work
//    * does this matter too much??
```

```rust
trait RetFuture {
    async fn method(&self) -> u32;
}

trait RetFuture {
    fn method(&self) -> impl Future<Output = u32>:
}
```

```rust!
trait HasOpaqueAT {
    type AT;
    fn gives(&self) -> AT;
    fn takes(&self, at: AT);
}

// In theory this could be `dyn*`-capable if you transform it such that `gives` returns an opaque thing and `takes` takes that same opaque thing.
```

---

Summary:

* Could redefine "dyn compatibility" to mean "cannot implement the trait"
    * means `&dyn Trait` only calls subset of dyn-compatible methods
    * would allow us to e.g. have "missing associated types" but they rule out parts of the trait
    * however means that `dyn Trait` does not (always) implement `Trait`
    * similar-but-different to `where Self: Sized`
    * might give an extra degree of freedom to "fix dyn-compatible rules"
    * seems like we have some alignment here!
* Could have a name for "the subset of the trait that is dyn compatible"
    * but it is not a "single" subset (`dyn Iterator` / `dyn Iterator<Item = u32>`)
    * Or more generally, a trait transformer that can subset or transform method signatures to make them dyn compatible.
    * Name placeholder: `impl dyn Trait`
* Generic method that take APIT (or an APIT-like pattern) can sometimes be made virtual
    * you instantiate the generic method with `APIT=&mut dyn Trait` and put that in the vtable
    * this only works if `&mut dyn Trait: Trait` (see above) but it gets broken by `'static` bounds and some other corner cases
* Something like `dyn*` would also expand because `fn(self)` would work and many other limitations arise from `dyn Foo` not being `Sized`
* Could add an `impl dyn Trait` (NEED A BETTER NAME) that is an `impl Trait` that only lets you call the dyn-capable subset, and you can then pass any `dyn Trait` to it.