Is `impl Trait1 for dyn Trait2` ever useful?

Today I was messing around and wrote this code:

use std::fmt::Display;

trait Tr {
    fn print(&self);
}

impl Tr for dyn Display { // <- some weird weirdness
    fn print(&self) {
        println!("{}", self);
    }
}

fn main() {
    let a = "lala";
    Tr::print(&a);
}

Compilation fails saying that

     Tr::print(&a);
               ^^ the trait `Tr` is not implemented for `&str`

If I replace line 7 (impl Tr for dyn Display) with impl<T: Display> Tr for T, it works as expected, printing lala.

Why is impl Tr for dyn Display valid syntax? Is there another case where it can be used legitimately?

2 Likes

It can be useful to feature dyn Traits with "generic methods":

#[dyn_safe(true)] // Error, `SpawnFut` cannot be made into a `dyn` object
trait SpawnFut {
    fn spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    ;
}

In that case, when there is a way to "go dyn and back", such as impl Future -> Pin<Box<dyn Future>> : impl Future, it is possible to feature a dyn SpawnFut API:

use ::core::{future::Future, pin::Pin};

#[dyn_safe(true)] // <- objective
trait SpawnFut : NonGenericSpawnFut {
    fn spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    where
        Self : Sized,
    ;
}

fn objective (s: &'_ dyn SpawnFut)
{
    s.dyn_spawn_fut(async {});
}

fn main ()
{
    struct Implementor();
    
    impl SpawnFut for Implementor {
        fn spawn_fut (&self, _: impl 'static + Future<Output = ()>)
        where
            Self : Sized,
        {}
    }

    objective(&Implementor())
}

// ---

trait NonGenericSpawnFut {
    fn non_generic_spawn_fut (&self, f: Pin<Box<dyn 'static + Future<Output = ()>>>)
    ;
}

impl<S : SpawnFut> NonGenericSpawnFut for S {
    fn non_generic_spawn_fut (&self, f: Pin<Box<dyn 'static + Future<Output = ()>>>)
    {
        self.spawn_fut(f)
    }
}

impl dyn '_ + SpawnFut {
    fn dyn_spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    {
        self.non_generic_spawn_fut(Box::pin(f))
    }
}
  • Playground

  • Ideally, .dyn_spawn_fut() could be named .spawn_fut(), for even more magic, but for some reason Rust gets confused in that case :frowning:


Now, this example does not feature impl Trait for dyn …, only impl dyn …; but it's easy to imagine how the former can still be useful: imagine that SpawnFut comes from an external crate, and that they did not feature that very convenient .dyn_spawn_fut() method.

In that case you could use the extension trait pattern to imbue dyn SpawnFut with your own convenience methods:

use ::external_crate::SpawnFut;

/// Extension trait!
#[dyn_safe(false)] // No more need for `dyn` here.
trait DynSpawnFut {
    /// Desired method's signature that we want to add.
    fn dyn_spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    ;
}

impl DynSpawnFut for dyn '_ + SpawnFut {
    fn dyn_spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    {
        self.non_generic_spawn_fut(Box::pin(f))
    }
}

Another example is to handle the case where somebody may write:

#[dyn_safe(true)]
trait SpawnFutAndMore : SpawnFut { … }

And then they would like to call .dyn_spawn_fut() on a dyn SpawnFutAndMore. With the inherent impl, this would not be possible. But with the extension trait approach, one can easily write:

impl<DynObject : ?Sized> DynSpawnFut for DynObject
where
    DynObject : SpawnFut,
{
    fn dyn_spawn_fut (&self, f: impl 'static + Future<Output = ()>)
    {
        self.non_generic_spawn_fut(Box::pin(f))
    }
}

then,

fn objective (s: &'_ dyn SpawnFutAndMore)
{
    s.dyn_spawn_fut(async { … });
}

will work :slightly_smiling_face:

1 Like

Oh, wow, I see. So one can do the (utterly useless)

use std::fmt::Debug;

trait Tr1: Debug { }

trait Tr2 {
    fn act(&self);
}

#[derive(Debug)]
struct St;

impl Tr1 for St { }

impl dyn Tr1 { }

impl Tr2 for dyn '_ + Tr1 {
    fn act(&self) {
        println!("{:?}", self);
    }
}

fn main() {
    let a = St;
    <dyn Tr1 as Tr2>::act(&a);
}

Independent of utility, it would be inconsistent if it wasn't. dyn Trait is a concrete type (unsized, but so are str and [T]).

5 Likes

The standard library directly implements Debug for the trait object type dyn Any + 'static and its variations:

https://doc.rust-lang.org/std/any/trait.Any.html#trait-implementations

That module also has several methods that are implemented on dyn Any + 'static (etc.). Admittedly Any is an oddball trait but hopefully this helps illustrate how impl blocks can be useful for trait object types.

Why wouldn't it be valid? You don't have to forbid something just because it isn't immediately or obviously useful.

1 Like

I'm still not sure I understand what's the extra benefit of impl Trait1 for dyn Trait2 compared to impl<T: Trait2> Trait1 for T. I currently understand that it is useful when I want to implement Trait1 for all trait object values that implement Trait2 while excluding concretely typed values that implement Trait2 (e.g. simple structs on the stack), but this doesn't sound quite right.

First, the alternative is actually impl<T: Trait2 + ?Sized> Trait1 for T since dyn Trait2 is !Sized.

Second, blanket implementations (impl<T: ...> A for T) are very constraining in terms of writing additional custom trait impls due to coherence, so if you can get away with a non-blanket impl Trait1 for dyn Trait2 you probably want that.

2 Likes

First, the alternative is actually impl<T: Trait2 + ?Sized> Trait1 for T since dyn Trait2 is !Sized .

Very right, thanks.

Second, blanket implementations ( impl<T: ...> A for T ) are very constraining in terms of writing additional custom trait impl s due to coherence, so if you can get away with a non-blanket impl Trait1 for dyn Trait2 you probably want that.

I'm not sure I get the intuition here. Intuitively coherence is all about external/internal traits/types (right?). Using blanket or non-blanket impls does not change whether said traits/types are internal or external. What nuance am I missing? A concrete example would be of help.

I believe they are referring to situations similar to this:

trait Trait {}
impl<T: Copy> Trait for T {}
impl<T> Trait for Vec<T> {}

Which fails because

upstream crates may add a new impl of trait std::marker::Copy for type std::vec::Vec<_> in future versions

Or something I've ran into a number of times, the blanket impl of impl<T, U> TryFrom<U> for T where U: Into<T> prevents you from implementing TryFrom generically on your own types.

2 Likes

An example. Given some trait:

trait Object { /* … */ }

then, the following impl causes a compilation error because of coherence issues / potential overlapping impls:

impl<T : ?Sized> ::core::fmt::Debug for T
where
    T : Object,
{
    fn fmt (self: &'_ Self, f: &'_ mut ::core::fmt::Formatter<'_>)
      -> ::core::fmt::Result
    {
        /* … */
    }
}

whereas the following one does not:

impl ::core::fmt::Debug for dyn '_ + Object {
    fn fmt (self: &'_ Self, f: &'_ mut ::core::fmt::Formatter<'_>)
      -> ::core::fmt::Result
    {
        /* … */
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=be69395f53aee9f58a45ca7c0b0a833d


For those dismissing this example by considering coherence issues to be kind of a quirk: more generally, there exist properties which hold for dyn Trait, but which do not necessarily hold for all impl Trait

  • (in the above example, the property was that of being local to the crate defining the impl, hence the coherence issue).

Another (simple?) example would be to have the crate defining the Future trait write:

unsafe impl<T> Sync for dyn '_ + Future<Output = T> {}
// as well as:
unsafe impl<T> Sync for dyn '_ + Send + Future<Output = T> {}

More generally, take any trait having no &self-based methods whatsoever (including supertraits; and no weird contracts / invariants): only basic &mut self methods (or self: Self / self: Box<Self>). Then it is sound to impl Sync for the trait object, since the set of API calls that can be done on such a &dyn Trait (such as &dyn Future… in the example) is empty and thus unable to cause unsoundness.

On the other hand, it would be completely unsound to write such an impl in a generic manner, since a concrete type may impl Trait while also having other methods that are &-based.

1 Like

Indeed,

use std::fmt::Display;

trait Trait {}
impl Trait for dyn Display {} // instead of `impl<T: Display> for T {}`
impl<T> Trait for Vec<T> {}

compiles successfully.

Doesn't this though constitute a violation of the spirit of the coherence rules? If Vec<_> gets an upstream blanket impl for Display in the future (wildly unlikely, I know), won't the program above stop compiling, (as there will be 2 impls of Trait for Vec<_>, one from the upstream impl and one from my impl<T> Trait for Vec<T> {})? Or me using the dyn version is me saying "I want this to work only as long as there is no Display impl for Vec<_> upstream"? Or the program will still compile and there is some implicit (!) way of choosing which impl to use?

(Note that I replaced Copy [used by @quinedot] with Display because the former isn't object-safe, so I couldn't use dyn Copy.)

I'm missing something, so I can't follow this reasoning. Why having &self methods may lead to unsoundness? Why not having such methods precludes unsoundness?

This confuses me, especially given that I can write

trait Tr1 {
    fn act(&self);
}

trait Tr2 {
    fn act_as_well(&self); // &self method
}

impl Tr1 for dyn Tr2 {
    fn act(&self) { } // &self method
}

struct St;

impl Tr2 for St {
    fn act_as_well(&self) { }
}

fn main() {
    let a = St;
    Tr1::act(&a as &dyn Tr2);
}

with no compilation error.

No, because Vec<T>, even when Vec<T> : Display, is not the type dyn Display.

While you could coerce a pointer to a Vec<T> to a pointer (of the same kind) to a dyn Display, the coercion is performing a type change (and the pointer, in and of itself, actually changes, getting "fattened" with dyn Display metadata):

trait MyDisplay {} // mock our being stdlib.

struct MyType;

impl MyDisplay for Vec<MyType> {}

fn main ()
{
    let mut v = Box::new(vec![MyType]);
    let _: &dyn MyDisplay = &*v /* as &dyn Display */; // Works
    let _: &mut dyn MyDisplay = &mut *v; // OK
    // But _some_ coercions being possible does not mean the types are the same:
    let _: &Box<dyn MyDisplay> = &v;
    // error[E0308]: mismatched types
    //   --> src/main.rs:13:34
    //    |
    // 13 |     let _: &Box<dyn MyDisplay> = &v;
    //    |            -------------------   ^^ expected trait object `dyn MyDisplay`, found struct `Vec`
    //    |            |
    //    |            expected due to this
    //    |
    //    = note: expected reference `&Box<dyn MyDisplay>`
    //               found reference `&Box<Vec<MyType>>`
    let _: Box<dyn MyDisplay> = v; // OK
}

Remember:

and that type is distinct from that of its sized implementors.


Incorrectly implementing Sync can lead to unsoundness, since you can have data races in that case.

What I meant so say, but which I may have phrased poorly, is that:

  • no matter the implementors that may get type-erased into a dyn Future, even non-Sync ones such as MyNonSyncType:

    Example of a MyNonSyncType definition:
    mod privacy_boundary {
        #[derive(Default)]
        pub
        struct MyNonSyncType /* = */ (
             ::core::cell::Cell<u64>,
        );
    
        impl MyNonSyncType {
            pub
            fn increment (self: &'_ MyNonSyncType)
            {
                self.0.set(self.get() + 1);
            }
    
            pub
            fn get (self: &'_ MyNonSyncType)
              -> u64
            {
                self.0.get()
            }
        }
    
        use ::core::{future::Future, pin::Pin, task::{Context, Poll}};
    
        impl Future for MyNonSyncType {
            type Output = ();
    
            fn poll (self: Pin<&'_ mut MyNonSyncType>, _: &'_ mut Context<'_>)
              -> Poll<()>
            {
                Poll::Ready(())
            }
        }
    }
    

    Since the .increment() method is not thread-safe, and since it is &self-based, it means MyNonSyncType cannot be Sync. And, indeed, by default / without unsafe, it won't be.


  • it would still be sound / correct to have an unsafe impl Sync for dyn Future<Output = …>, because Future has no &self-based APIs whatsoever (so, for instance, it will always be impossible to find a code path originating from a &dyn Future where the .increment() of MyNonSyncType gets called: having no &self API whatsoever is a sufficient condition (w.r.t. the actual definition fo Sync: that all the &-based APIs for that type (if any) be thread-safe).

    • bonus rant

      If we really wanted to, an example of a trait OtherTrait where one could not soundly impl Sync for &dyn OtherTrait is Display: I could, for instance, impl Display for MyNonSyncType, and call .increment() in the .fmt() method. That means that from a &dyn Display, it would be possible to call .increment(), which means dyn Display cannot be Sync. But we are getting a bit out of topic here :sweat_smile:

    (I guess the confusion stems from that being a sufficient condition, but obviously not a necessary one: there exist types and traits with thread-safe &self-based methods exclusively, and one could soundly impl Sync for those.)

But regarding a blanket impl such as:

unsafe
impl<R, T : ?Sized> Sync for T
where
    T : Future<Output = R>,
{}

which would be your "strictly more general" counterpart to:

unsafe
impl<R> Sync for dyn '_ + Future<Output = R>
{}

then I have showcased that there is indeed a difference: the latter is sound, whereas the former is not (it would, among other things, impl Sync for MyNonSyncType when T = MyNonSyncType).

2 Likes

There won't be two impls of Trait for Vec<_>. Implementing on dyn Display doesn't mean "implement this on every T: Display", it means "implement on the concrete type which is dyn Display, that compiler construct that consists of a data pointer and a vtable for method dispatch". The only way you can use the additional impl Trait-ness of a Vec<_> with Display implemented would be to coerce the Vec<_> into a different type -- a dyn Display. (As things work currently, since dyn _ is unsized, this also means putting it behind a reference or in a Box.)

Perhaps think of it this way: a &Vec<_> and a &[_] could have distinct methods with the same name and signature, but they're still distinct methods, even though the former coerces into the latter.

Here's a Vec-like that gains a Display-like impl, but there is no coherence conflict.

In this case it's not really implicit. It depends on the concrete type. There is a precedence when you have multiple trait methods with the same name or trait methods and inherent methods on the type: the inherent methods take precedence, and otherwise you need to use the fully qualified path to disambiguate. And there is a method resolution algorithm as well.

1 Like

Thank you, I now get it much better. The key seems to be that dyn Trait is a concrete type to which other types are explicitly converted (e.g. by Struct as dyn Trait). So when I impl a dyn Trait type there's nothing generic about it, I'm not providing an implementation for every type that fits some constraints. Users of my impl have to explicitly opt-in, so no need for coherence rules. (The massive discussion on making dyn explicitly required also now makes so much more sense.)

In contrast, with a generic/blanket impl I (purposefully) kind of squat implementations for all compatible types, thus the need for coherence rules.

This is another tradeoff between flexibility and runtime overhead, different from (but related to) Vec<&dyn Trait> vs Vec<Struct>.

Please correct me if I'm wrong.

2 Likes

I think you get it or mostly get it now, so these are just nits.

Right, it's a concrete, static type -- for dynamic dispatch of trait methods, but still static. There are some implicit coercions that can happen, but something (return type, parameter type, explicit variable type...) has to be "expecting" the dyn Trait, as it were. (In particular it's a kind of unsized coercion.)

There are always coherence rules in play for trait implementations (you can't implement the same trait twice on a concrete type even if you don't use generics), but it's much easier to avoid ambiguity when you don't use blanket rules.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.