Duplicate definition while impl specialization for generic struct

I have a struct with a generic parameter, and a corresponding generic impl which defines some functions.

struct Foo<T>(T);

impl<T> Foo<T>
where T: Debug
{
    pub fn print(&self) {
        println!("blanket: {:?}", self.0);
    }
}

I would like to specialize fn print for a specific type.

impl Foo<f64> {
    pub fn print(&self) {
        println!("f64: {}", self.0);
    }
}

However, this fails with duplicate definitions for print.

Full code in playground.

As per impl specialization RFC, this should be possible:

At the simplest level, specialization is about allowing overlap between impl blocks, so long as there is always an unambiguous "winner" for any type falling into the overlap.

Note that this example doesn't implement any trait on the type, but deals with overlaps in the type's impl blocks themselves. Though the RFC's examples are mostly concerned with implementation of a trait in an overlapping manner, I believe the same applies here as well.

...permits multiple impl blocks to apply to the same type/trait, so long as one of the blocks is clearly "more specific" than the other.

Is there a way to make this work? Am I missing some syntax here, or does it work only if print is a function in a trait that Foo implements?


PS: This thread looks related. But, it deals with multiple impl blocks with different functions, without any overlap.

Specialization has to do with impls of traits, but you are not implementing a trait here.

Also note that specialization is not available on stable yet. You'll have to use the nightly compiler and write #![feature(specialization)] or #![feature(min_specialization)] at the top of your lib.rs/main.rs file in order to enable the feature.

In general RFC features aren't necessarily available in stable, and sometimes they aren't even implemented in the nightly compiler.

1 Like

You are right. But the wording in the RFC wasn't very clear to me (highlighted in my original post), I assumed it would work without traits.

Thanks. This is what worked for me:

  • #![feature(min_specialization)] at the top.
  • A trait with the function that needs to be specialized.
  • Blanket impl of the trait for Foo<T> with default keyword before the function.
  • Specialized impl of the trait over Foo<f64>.

full code

That's new. That's for pointing out. As the RFC is marked merged, I assumed it would have made into stable by now.

Unstable Features (The Book)
List of nightly features

Specialization is by definition about generics (this is not Rust-specific terminology, it exists in many languages with parametric generics), so any impl blocks mentioned are necessary impl Trait blocks.

(In fact, the problems with specialization arise exactly because of generics. When precise, leaf-level types are exhaustively known not to overlap, resolving methods is trivial insofar as you consider a CSP nontrivial. Issues emerge when we only know that a type might be anything that satisfies a given trait bound, and we have to reason over universally-quantified sets of types and type-level functions, not only single concrete types.)

If you mean this PR, then that's just for defining the actual details of the proposed feature. After they're defined and the RFC is merged then the work for implementing it can start.

For tracking the status of the implementation of an RFC there's usually a link to a "rust issue" or "tracking issue" where it's tracked the status of the actual implementation and stabilization of the RFC. For example for speciallization there's this issue and as you can see there's a lot of work that has yet to be done.

Exactly. As the original type itself is parameterized by a generic type, Foo<T>, to me it still makes sense to have blanket and specialized impl for different Ts. I don't see why traits must be involved here, but I understand that's how it'll be implemented in Rust.

Checkout this C++ code. It's very similar to my original rust code: defines a class Foo parameterized by type T, there's a blanket and special impl for the member method.

template<typename T>
class Foo {
public:
    void print();
};

template<typename T>
void Foo<T>::print() {
    cout << "Blanket impl" << endl;
}

template<>
void Foo<int>::print() {
    cout << "int impl" << endl;
}

int main()
{
    Foo<float> floatfoo;
    Foo<int> intfoo;
    floatfoo.print(); // Blanket impl
    intfoo.print();   // int impl
    return 0;
}

Runnable code


@SkiFire13 I got confused with the merging of the RFC itself and the actual implementation of RFC. Thanks.