Help make this code compile 2

I'm not understanding @the8472 and @CAD97 's answers in Stable rust: specialize generics - #4 by the8472 so I feel like I screwed up the problem definition / used a wrong term there.

Here is a 2nd attempt.

Can we make the following code compile? There is one path for serializing [T; N] in general, but I want a separate (maybe optimized) path for serializing [u8; N]

Is this possible in stable Rust ?

pub trait My_Serialize {
    fn write(&self, w: &mut dyn Write);
}

// we can serialize a u8
impl My_Serialize for u8 {
    fn write(&self, w: &mut dyn Write) {
        w.write_u8(*self).unwrap();
    }
}

// how to serialize arrays in general
impl<const N: usize, T: My_Serialize> My_Serialize for [T; N] {
    fn write(&self, w: &mut dyn Write) {
        for x in self {
            T::write(x, w);
        }
    }
}

// can we do an optimzied for u8 arrays ?
impl<const N: usize> My_Serialize for [u8; N] {
    fn write(&self, w: &mut dyn Write) {
        w.write_all(self).unwrap();
    }
}

Here a snippet of what I think both meant to tell you:

use std::any::TypeId;
use std::io::Write;

pub trait My_Serialize {
    fn write(&self, w: &mut dyn Write);
}

// we can serialize a u8
impl My_Serialize for u8 {
    fn write(&self, w: &mut dyn Write) {
        w.write(&[*self]).unwrap();
    }
}

// how to serialize arrays in general
impl<const N: usize, T: My_Serialize + 'static> My_Serialize for [T; N] {
    fn write(&self, w: &mut dyn Write) {
        if TypeId::of::<T>() == TypeId::of::<u8>() {
            // SAFETY: we know `T` is `u8`
            w.write_all(unsafe { &*(self.as_slice() as *const [T] as *const [u8]) })
                .unwrap();
            return;
        }

        for x in self {
            T::write(x, w);
        }
    }
}

Playground.

Your snippet will not be possible given the usual specialization problems. T could be u8, so there could be overlapping implementations of My_Serialize for [u8; N].

2 Likes

I think the closest you can get will be something like this:

impl<const N: usize, T: My_Serialize + 'static> My_Serialize for [T; N] {
    fn write(&self, w: &mut dyn Write) {
        use std::any::Any;
        if let Some(bytes) = (self as &dyn Any).downcast_ref::<[u8; N]>() {
            w.write_all(bytes).unwrap();
        } else {
            for x in self {
                T::write(x, w);
            }
        }
    }
}

Edit: Looks like @jofas got there at basically the same time as me. The only real difference between our versions is that mine uses the unsafe code inside downcast instead of writing it myself. That makes the optimizer work a little bit harder but, in my experience, it's always succeeded in devirtualizing this pattern of dyn Any use.

4 Likes

Another approach is to define an associated function on My_Serialize with a default implementation that can be overridden for the special cases:

pub trait My_Serialize {
    fn write(&self, w: &mut dyn Write);
    fn write_slice(items: &[Self], w: &mut dyn Write) where Self:Sized {
        for x in items {
            x.write(w)
        }
    }
}

impl My_Serialize for u8 {
    fn write(&self, w: &mut dyn Write) {
        w.write(&[*self]).unwrap();
    }
    
    fn write_slice(items: &[u8], w: &mut dyn Write) {
        w.write_all(items).unwrap();
    }
}

impl<const N: usize, T: My_Serialize> My_Serialize for [T; N] {
    fn write(&self, w: &mut dyn Write) {
        T::write_slice(self, w);
    }
}
3 Likes