rustnewtype

How to fix runtime overhead caused by newtype in array


Rust claims the newtype pattern has no runtime overhead. However, I ran into cases where the statement seems not correct. Consider the following Rust program:

pub struct MyInt (i32);

pub fn f(xs: [i32; 3]) -> i32 {
    xs[0]
}

pub fn g() -> i32 {
    let xs = [MyInt(1), MyInt(2), MyInt(3)];
    let myxs = xs.map(|x| x.0);
    f(myxs)
}

Given an API like the function f, which takes an array of i32, I have to map over my array of MyInt in order to call it. If I understand it correctly, this will create a new array, which seems not to be "runtime overhead free". Did I miss anything? Or is this considered as a misuse of the new type pattern?

I'd appreciate your help.

I didn't find much discussion on the topic of a newtype used in an array or other containers.


edit:

After reading the comments, I realized that the problem is more about array rather than the newtype. However, if I use the slice, to the best of my knowledge, I would need the following program

pub struct MyInt (i32);

pub fn f(xs: &[i32]) -> i32 { xs[0] }

pub fn g() -> i32 {
    let xs = [MyInt(1), MyInt(2), MyInt(3)].as_slice();
    let myxs: &[i32] = unsafe { std::mem::transmute(xs) };
    f(myxs)
}

or with vectors

pub struct MyInt (i32);

pub fn f(xs: Vec<i32>) -> i32 { xs[0] }

pub fn g() -> i32 {
    let xs = vec![MyInt(1), MyInt(2), MyInt(3)];
    let myxs: Vec<i32> = xs.into_iter().map(|MyInt(x)| x).collect();
    f(myxs)
}

However, it seems I can't get away with the unsafe transmute in the array version and I still have to pay for the cost of creating a new vector in the vector version. Are there any better approaches?


Solution

  • Transmuting slices or Vec is only safe if the newtype is marked #[repr(transparent)] (or #[repr(C)]). If it is, you can use bytemuck to do it safely:

    #[derive(Clone, Copy, bytemuck::NoUninit)]
    #[repr(transparent)]
    pub struct MyInt(i32);
    
    pub fn f(xs: &[i32]) -> i32 { xs[0] }
    
    pub fn slice() -> i32 {
        let xs = [MyInt(1), MyInt(2), MyInt(3)];
        let myxs: &[i32] = bytemuck::cast_slice(&xs);
        f(myxs)
    }
    
    pub fn vec() -> i32 {
        let xs = vec![MyInt(1), MyInt(2), MyInt(3)];
        let myxs: Vec<i32> = bytemuck::cast_vec(xs);
        f(&myxs)
    }