Pre-RFC: AsDynRef and AsDynMut

Please tell me what you think!

Summary

These traits allow converting any object to a trait object. Specifically,

  • AsDynRef converts &T to &dyn SomeTrait where T: SomeTrait
  • AsDynMut converts &mut T to &mut dyn SomeTrait where T: SomeTrait

Motivation

I recently read a blog post explaining how to create a trait object whose concrete type is unknown at compile time. The optimal solution turns out to be complicated and non-obvious.

This proposal tries to change that; it allows us to implement the AsDynRef and AsDynMut traits for wrapper types. For example, we can implement them for an enum with two variants:

enum Either<L, R> {
    Left(L),
    Right(R),
}

impl<'a, A, B, TraitObj> AsDynMut<'a, TraitObj> for Either<A, B>
where
    A: AsDynMut<'a, TraitObj>,
    B: AsDynMut<'a, TraitObj>,
    TraitObj: ?Sized + 'a,
{
    fn as_dyn_mut(&'a mut self) -> &'a mut TraitObj {
        match self {
            Either::Left(l) => l.as_dyn_mut(),
            Either::Right(r) => r.as_dyn_mut(),
        }
    }
}

This gives us a nice way to create a trait object, without knowing the concrete type at compile time:

let mut readable = if some_condition {
    Either::Left(stdin())
} else {
    Either::Right(File::open(path)?)
};

accepts_read_trait_object(readable.as_dyn_mut());

Guide-level explanation

The traits are defined like this:

pub trait AsDynRef<'a, TraitObj: ?Sized + 'a>: Sized {
    fn as_dyn_ref(&'a self) -> &'a TraitObj;
}

pub trait AsDynMut<'a, TraitObj: ?Sized + 'a>: Sized {
    fn as_dyn_mut(&'a mut self) -> &'a mut TraitObj;
}

These traits are automatically implemented for all types, for all traits they implement, if they're object safe.

An example how they would be implemented for types that implement Read:

impl<'a, T: Read> AsDynRef<'a, dyn Read + 'a> for T {
    fn as_dyn_ref(&'a self) -> &'a (dyn Read + 'a) {
        self
    }
}

impl<'a, T: Read> AsDynMut<'a, dyn Read + 'a> for T {
    fn as_dyn_mut(&'a mut self) -> &'a mut (dyn Read + 'a) {
        self
    }
}

Since there is currently no mechanism to implement these traits for all trait objects, they have to be built into the compiler.

Drawbacks

This adds two more built-in traits, making the language more complicated.

5 Likes

How are you going to resolve the conflicting implementation when we impl TraitObj for Either<A, B>?


And if you don't use a trait, this feature already exists via the (unstable) Unsize marker trait.

#![feature(unsize)]
use std::marker::Unsize;

pub enum Either<L, R> {
    Left(L),
    Right(R)
}
impl<L, R> Either<L, R> {
    fn as_dyn_ref<U>(&self) -> &U 
    where 
        U: ?Sized,
        L: Unsize<U>,
        R: Unsize<U>,
    {
        match self {
            Either::Left(a) => a,
            Either::Right(b) => b,
        }
    }
}

fn main() {
    for i in 0..2 {
        let f = if i == 0 {
            Either::Left(|x| x + 1)
        } else {
            Either::Right(|x| x - 1)
        };
        
        let g = f.as_dyn_ref::<dyn Fn(i32) -> i32>();
        dbg!(g(5));
        // expected output:
        //   g(5) = 6
        //   g(5) = 4
    }
}
3 Likes

We don't need to implement TraitObj, that's just a type variable. However, it's perfectly fine to implement AsDynRef or AsDynMut for a type multiple times. For example, a type can implement both AsDynMut<dyn Read> and AsDynMut<dyn Write>. There's no conflict.

Wow, I wasn't aware of that!

P.S. if you use the either crate, you don't have to implement anything for Either<L, R>, because it implements Deref(Mut) for all traits implemented by both L and R. So if both variants implement AsDynMut, you can call .as_dyn_mut() on Either.

What I mean is if we impl Read for Either, then the custom impl<U> AsDynRef<U> for Either will conflict with the generated impl AsDynRef<dyn Read> for Either.

1 Like

I think you misunderstood me. If the traits are auto-implemented, we don't need to implement them for anything explicitly.

However, since you told me that the Unsize trait can do something similar, I don't think they're necessary anyway :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.