Jonathan Woollett-light
Jonathan Woollett-light

Reputation: 3243

How can I check, at compile-time, that a slice is a specific size?

I'd like to check, at compile-time, that a slice used in a From implementation is a specific size.

(Playground)

#[derive(Debug)]
struct Pixel {
    r: u8,
    g: u8,
    b: u8,
}

impl From<&[u8]> for Pixel {
    fn from(arr: &[u8]) -> Pixel {
        Pixel {
            r: arr[0],
            g: arr[1],
            b: arr[2],
        }
    }
}

fn main() {
    println!("Hello, world!");

    let arr: [u8; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    let pixels: Vec<Pixel> = arr.chunks_exact(3).map(Pixel::from).collect();
    println!("{:.?}", pixels);
}

This is not as specific as I'd like. I'd like to check the arr passed to Pixel::from<&[u8]>() is 3 elements as clearly as possible (at compile time).

Thought of assert!(arr.len()==3), but this checks at runtime.

So I thought maybe I could do the conversion by (Playground):

impl From<[u8; 3]> for Pixel {
    fn from(arr: [u8; 3]) -> Pixel {
        Pixel {
            r: arr[0],
            g: arr[1],
            b: arr[2],
        }
    }
}

but this leads to:

error[E0277]: the trait bound `Pixel: From<&[u8]>` is not satisfied
   --> src/main.rs:22:30
    |
22  |       let pixels: Vec<Pixel> = arr.chunks_exact(3).map(Pixel::from).collect();
    |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&[u8]>` is not implemented for `Pixel`
    |
    = help: the following implementations were found:
              <Pixel as From<[u8; 3]>>

error[E0277]: the trait bound `Pixel: From<&[u8]>` is not satisfied
  --> src/main.rs:22:54
   |
22 |     let pixels: Vec<Pixel> = arr.chunks_exact(3).map(Pixel::from).collect();
   |                                                      ^^^^^^^^^^^ the trait `From<&[u8]>` is not implemented for `Pixel`
   |
   = help: the following implementations were found:
             <Pixel as From<[u8; 3]>>
   = note: required by `from`

error[E0277]: the trait bound `Pixel: From<&[u8]>` is not satisfied
   --> src/main.rs:22:30
    |
22  |       let pixels: Vec<Pixel> = arr.chunks_exact(3).map(Pixel::from).collect();
    |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<&[u8]>` is not implemented for `Pixel`
    |
    = help: the following implementations were found:
              <Pixel as From<[u8; 3]>>

Similarly I tried From<&[u8; 3]> but same result.

Is there a way to implement from for a specific sized slice?

This is a not a duplicate of How to convert a slice into an array reference? as this question specifically relates to checking at compile-time without runtime performance effects, it is not casting &[u8] to &[u8; 3] rather simply checking at compile time &[u8] has 3 elements (which may be done via using &[u8; 3]). All answers to the aforementioned question incur runtime affects, except I believe this approach in this answer (applied like this) but this does not check at all that the slice is the appropriate length. This question is not specifically about being able to use Pixel::from<[u8;3]> but rather about generally checking the length at compile time, which none of these answers offer or relate to.

Upvotes: 2

Views: 1027

Answers (1)

Shepmaster
Shepmaster

Reputation: 430741

You cannot do this at compile time because slice lengths are not known at compile time. That's a big reason that slices exist in the first place. When the length is known at compile time, that's an array.

See also:


I'd instead write both fallible and infallible conversions:

use std::array::TryFromSliceError;
use std::convert::TryFrom;

#[derive(Debug)]
struct Pixel {
    r: u8,
    g: u8,
    b: u8,
}

impl TryFrom<&[u8]> for Pixel {
    type Error = TryFromSliceError;

    fn try_from(arr: &[u8]) -> Result<Self, Self::Error> {
        <&[u8; 3]>::try_from(arr).map(Self::from)
    }
}

impl From<&[u8; 3]> for Pixel {
    fn from(arr: &[u8; 3]) -> Self {
        Self::from(*arr)
    }
}

impl From<[u8; 3]> for Pixel {
    fn from(arr: [u8; 3]) -> Self {
        Pixel {
            r: arr[0],
            g: arr[1],
            b: arr[2],
        }
    }
}

Then you can convert from an array, allowing for a compile time error, or when you have a slice and don't know the length at compile time, you can attempt to convert and have a run-time error.

In the future, you can use methods like slice::array_chunks to convert a slice into an iterator of arrays. However, there's still the case that the slice wasn't the right length (too long or short) that you have to handle somehow.

Upvotes: 6

Related Questions