Partial specialization of a generic struct


#1

Why does the following code not compile?

pub struct GenVal<T>(pub T);

impl<T> GenVal<T> { 
    pub fn value(&self) ->&T {
        println!("Generic value() function called.");
        &self.0
    }
}

impl GenVal<f64> {
    pub fn value(&self) ->&f64 {
        println!("f64 value() function called.");
        &self.0
    }
}

fn main() {
    let z = GenVal(3.14);
    let z_val = z.value();
}

Playpen link: http://is.gd/zR1kxt


#2

Okay, I think I jumped the gun on this one. Rust probably does not support partial specialization (and operator overloading). The following does not work either…

use std::fmt::Debug;

#[derive(Debug)]
struct SGen<T: Debug>(T); // Generic type `SGen`.


fn generic(s: SGen<i32>) {
    println!("i32 version called. s={:?}", s);
}

fn generic<T: Debug>(s: SGen<T>) {
    println!("Generic version called. s={:?}", s);
}

fn main() {
    generic(SGen(1.0));
    generic(SGen(6));
}

Instead you have to do the following…

use std::fmt::Debug;

#[derive(Debug)]
struct SGen<T: Debug>(T); // Generic type `SGen`.


fn gen_spec_i32(s: SGen<i32>) {
    println!("i32 version called. s={:?}", s);
}

fn generic<T: Debug>(s: SGen<T>) {
    println!("Generic version called. s={:?}", s);
}

fn main() {
    generic(SGen(1.0));
    gen_spec_i32(SGen(6));
}

So the original code can be made to work like so…

pub struct GenVal<T>(pub T);

impl<T> GenVal<T> { 
    pub fn value(&self) ->&T {
        println!("Generic value() function called.");
        &self.0
    }
}

impl GenVal<f64> {
    pub fn value_f64(&self) ->&f64 {
        println!("f64 value() function called.");
        &self.0
    }
}

fn main() {
    let z = GenVal(3.14);
    let _z_val_g = z.value();
    let _z_val = z.value_f64();
}

#3

I ran into this problem too and figured I needed negative trait bounds to make it work, so I just ended up ditching blanket implementations with generics for now.

Perhaps the issue is that specialisation isn’t applying to generic parameters; the GenVal<T> type encapsulates GenVal<f64>, because f64 is in the scope of T, so either impl is equally valid.

If I add another type parameter to GenVal, and use something different for the f64 implementation then it works because GenVal<T, ()> does not encapsulate GenVal<f64, Option<()>>. If the second type parameter is the same as the blanket impl then it fails again with the same error.


#4

There is plan to support specialization : https://github.com/aturon/rfcs/blob/impl-specialization/text/0000-impl-specialization.md. But it is not available in stable Rust yet.

Rust does not support function overloading, but it support custom operator definition by implementing Add, Sub, Mul, … traits


#5

I guess so but coming from a C++ background I feel C++ does the right thing in this case. It uses the most specialized implementation (GenVal<f64>::value()) from all available implementations when possible. The thinking is that the programmer will not bother writing a specialized version of a function unless it is beneficial over the generic version somehow.

Thanks for sharing the negative trait link but I still think the C++ way of doing this looks more elegant.


#6

Thanks for sharing this link. I think it is a better solution for this problem.