Why isn't the specialized implementation used?

Expected output:

Actual output: default

I didn't dig in depth, but the implementation getting called that prints default is the implementation for dyn Op<'_> + '_. Playground. Note that the bounds on that default implementation are met by dyn Op<'_> + '_.[1]

Incidentally my real answer is "don't use feature(specialization); not only is it incomplete, it's known to be unsound."


  1. There are some open questions about how this should interact with the compiler-provided implementation. ↩︎

4 Likes

Mainly, doing a search-and-replace of s/dyn Op/dyn Targets/g fixes the issue:

  • If you use dyn you probably want to be dynamically dispatching the .targets() method (in C++ parlance, call it as a virtual method);
  • but dyn Op… does not include such a method in its trait hierarchy.
  • You did not get an error because your blanket impl of Targets for T : ?Sized + Op… did cover the dyn Op… type, resulting in a statically dispatched call to the (default) fn targets in that impl.

I also agree that you should very much avoid using specialization, especially if you're not dealing with : 'static types :warning:

XY-ing a better design

Since you'll probably want to stick to dyn Op… since you want to be expressing that these things are Operands or Operations, another way not to have to replace dyn Op… with dyn Targets is by having Targets be a supertrait of trait Op, so that the methods of the former be part of the latter, including in the dynamic dispatch hierarchy.

From there, you can avoid the need for specialization at the cost of a very tiny bit of red tape:

  1. //                   no more `: Op<'a>` here.
    //                   v
    pub trait Targets<'a> {
        fn targets(&self) -> Vec<LabelOrNext<'a>> {
            // "default" impl here.
            println!("default");
            vec![]
        }
    }
    
    //                                    vvvvvvvvvvvvv
    pub trait Op<'a> : ::core::fmt::Debug + Targets<'a> {
        // ...
    }
    
    • (or you could merge fn targets inside of Op<'_>)
  2. Now, any concrete implementation of Op<'a> would need a companion concrete implementation of Targets<'_> (or of the fn targets method):

    • For your concrete Br<'_> type, you write it as usual;

    • For the other types, you just need to "tag/acknowledge" it for the above "default impl" to be used:

      struct MyOtherOp<…> {
          …
      }
      
      impl<'a> Op<'a> for MyOtherOp<…> {
          …
      }
      
      // 👇
      impl<'a> Targets<'a> for MyOtherOp<…> {} // <- no need for a body here!
      

Stable Rust Playground

2 Likes