2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jonathan Woollett-light
  • 2,813
  • 5
  • 30
  • 58
  • https://docs.rs/itertools/0.10.0/itertools/trait.Itertools.html#method.tuple_windows could be use but this will anyway do runtime check and could hide error – Stargateur May 26 '21 at 18:04
  • @Shepmaster Added explanation of why this is not a duplicate. So if I'm understanding this, it cannot be done in stable because it requires const generic and would be done with `slice::array_chunks`? If that's right that seems a reasonable if somewhat unsatisfying answer (having to simply wait till const generics become stable that is). – Jonathan Woollett-light May 26 '21 at 18:50

1 Answers1

6

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I would add, that this can be accomplished in nightly using [`slice::array_chunks`](https://doc.rust-lang.org/std/primitive.slice.html#method.array_chunks) like [this](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d6b253da179b3b8303f925720cac332c). – Jonathan Woollett-light May 26 '21 at 18:58
  • @JonathanWoollett-light but that _doesn't solve your problem_. You won't get a compiler error [when the slice isn't evenly divisible](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=eb79428f078e91a2ac8bbdcb79e1d825), which is what you've asked. – Shepmaster May 26 '21 at 19:00
  • It doesn't check `arr` is the appropriate size, but it does check the chunks are the appropriate length (`array_chunks::<4>()` will raise a compile time error while `chunks(4)` will not) a step in the direction of clarity I think at least. Originally this was what my focus was on (I messed up not presenting `arr` as a `Vec` I now realise, it was misleading I see). – Jonathan Woollett-light May 26 '21 at 19:08