Can I specialize const_generics?

Considering following code

#![feature(min_const_generics)]

struct SSS<const N:usize>{
}

impl<const N:usize> Clone for SSS<N>{
    fn clone(&self)->Self{
        SSS{}
    }
}

fn main() {
}

Above code just works.

Then can I specialization the impl Clone for a special N? i.e.,

impl Clone for SSS<0>{
    fn clone(&self)->Self{
        SSS{}
    }
}

which apparently cannot be compiled.

I have also tried some other method e.g.,

#![feature(const_generics)]

#[derive(PartialEq, Eq)]
enum NDim{
    ZERO,
    NONZERO(usize)
}

struct SSS<const N:NDim>{
}

impl Clone for SSS<{NDim::ZERO}>{
    fn clone(&self)->Self{
        SSS{}
    }
}

impl<const n:usize> Clone for SSS<{NDim::NONZERO(n)}>{
    fn clone(&self)->Self{
        SSS{}
    }
}


fn main() {}

You are trying to specialize with:

  • neither the specialization feature,

  • nor the default keyword to allow being specialized / overridden.

Granted, even with those two things const_generics might have been incompatible with specialization, but luckily, they are not :slightly_smiling_face::

#![feature(
    min_const_generics,
    specialization, // <- enable the `default` keyword
)]

struct S<const N: usize>;

impl<const N: usize> Clone for S<N> {
    default // <- allow being overridden - specialized impl overlap
    fn clone (self: &'_ Self) -> Self
    {
        Self
    }
}

impl Clone for S<0> {
    fn clone (self: &'_ Self) -> Self
    {
        Self
    }
}
6 Likes

I know this isn't entirely related, but was thinking about this the other day. What do you think @Yandros about "specializations" (new term will be needed) for enums? I don't see this feature added-in, but thought that it could open up an interesting coding style:

enum S {
    Animal(String, String, usize),
    Alien(String, usize),
    // anything with "default" must have the same number AND type of discriminants
    default Apples(usize)
}


default impl Clone for S {
    fn clone (self: &'_ Self) -> Self {
        // here, self.0 exists for access b/c of the default keyword
        println!("Clone on non-animal and non-alien type called");
        Self
    }
}

impl Clone for S::Animal {
    fn clone (self: &'_ Self) -> Self {
       // here, self.0, self.1, and self.2 exist for access
        println!("Clone on animal called");
        Self
    }
}

impl Clone for S::Alien {
    fn clone (self: &'_ Self) -> Self {
        // here, self.0 and self.1 exist for access
        println!("Clone on alien called");
        Self
    }
}

This would allow us to get rid of match and replace it with calling functions on enum variables directly. This would make the code look nicer and more maintainable in my opinion through the use of traits

1 Like

Note that variants are not distinct types today -- they're mostly functions -- so that impl doesn't make a ton of sense without something like https://github.com/rust-lang/rfcs/pull/1450 or https://github.com/rust-lang/rfcs/pull/2593.

Though personally when I see this I'd approach it a different way, where I'd make Animal, Alien, and Apples be standalone structs that all implement the same trait. Then for now have enum S { Animal(Animal), Alien(Alien), ... } (like how IpAddr does it) where I can implement the trait for the enum by forwarding to the internal trait, and maybe one day we'll have some sort of "enum impl Trait" to auto-implement said enum+trait.

1 Like

Yes, it looks like auto-derived impls for enums is what you are looking for.


If you want to keep something akin to your syntax, which is to swap the order of the match with the enum (in a way, your match is at the item statement), then a (probably procedural) macro could do it for you.

That is, something transforming:

match_impl! {
    enum S {
        Animal(String, String, usize),
        Alien(String, usize),
        // anything with "default" must have the same number AND type of discriminants
        #[match_impl(default)]
        Apples(usize),
    }

    impl Clone for match Self {
        Self::Animal => {
            fn clone (self: &'_ Self) -> Self {
                // here, self.0, self.1, and self.2 exist for access
                println!("Clone on animal called");
                Self(self.0.clone(), self.1.clone(), self.2)
            }
        },
        Self::Alien => {
            fn clone (self: &'_ Self) -> Self {
                // here, self.0 and self.1 exist for access
                println!("Clone on alien called");
                Self(self.0.clone(), self.1)
            }
        },
        default => {
            fn clone (self: &'_ Self) -> Self {
                // here, self.0 exists for access b/c of the default keyword
                println!("Clone on non-animal and non-alien type called");
                Self(self.0)
            }
        },
    }
}

into:

#[derive(Clone)]
enum S {
    Animal(Animal), 
    Alien(Alien),
    Apples(Apples),
}
struct Animal(String, String, usize);
struct Alien(String, usize);
struct Apples(usize);

impl Clone for Animal {
    fn clone (self: &'_ Self) -> Self {
        // here, self.0, self.1, and self.2 exist for access
        println!("Clone on animal called");
        Animal(self.0.clone(), self.1.clone(), self.2)
    }
}

impl Clone for Alien {
    fn clone (self: &'_ Self) -> Self {
        // here, self.0 and self.1 exist for access
        println!("Clone on alien called");
        Self(self.0.clone(), self.1)
    }
}

impl Clone for Apples {
    fn clone (self: &'_ Self) -> Self {
        // here, self.0 exists for access b/c of the default keyword
        println!("Clone on non-animal and non-alien type called");
        Self(self.0)
    }
}

That being said, when we compare the two syntaxes, the one for the macro is not that much short than the expanded one, and it does leads to a new "custom" syntax for the language; so I am personally not convinced it is that good of an idea :smile:, but maybe for your actual use case it could make sense?

1 Like