Unsafe conversion from slice to array reference

Hello,

is there a way in std to convert a slice to an array reference without the length check? If not, what would be the proper way? Would the following code be correct/idiomatic?

// SAFETY: length of slice must be equal to N
unsafe fn slice_to_array_unchecked<T, const N: usize>(slice: &[T]) -> &[T; N] {
    debug_assert!(slice.len() == N);
    &*(slice as *const _ as *const _)
}

fn main() {
    let v: Vec<i32> = vec![10, 20, 30];
    let array_ref: &[i32; 3] = unsafe { slice_to_array_unchecked(&v) };
    assert_eq!(array_ref, &[10, 20, 30]);
}

(Playground)

A slice pointer is fat, an array pointer is thin, so

looks pretty strange IMHO. I’d replace this with

slice.as_ptr() as *const _

Hmmm, or maybe this?

-    &*(slice as *const _ as *const _)
+    &*slice.as_ptr().cast()

(Playground)

2 Likes

Some roundabout ways and using unstable API… slice.as_chunks_unchecked().get_unchecked(0) :sweat_smile:


Ah, wait, this one actually works: slice.try_into().unwrap_unchecked().

3 Likes

This feels a bit workaround-y. :sweat_smile:

This is very descriptive, I like it.

(But my code above is correct nonetheless? I remember transmuting references is UB, so I thought going through a pointer is the way to go.)

Yes, absolutely.

1 Like

Oh but wait, is this zero-cost? I.e. will the optimizer be able to skip the check?

I’m sorry, this is not necessarily directed at you but also at other potential readers… but in general, it’s important to note that bound checks are usually not all that expensive, so only use unsafe for this if it’s actually benefiting performance, and if the use-cases are well reviewed.

So in most cases, the actually “idiomatic” approach would be to use safe rust and stick to .try_into().unwrap().

2 Likes

It seems to be able to for all I could tell in some small tests (click “ASM” in the drop-down next to “Build”).

1 Like

Yes, I normally refrain from using unsafe for that. My application case is a memory-mapped database (mmtkvdb), where corrupted on-disk data can lead to UB anyway (see also: Avoiding unsafe when using a C library: LMDB). So the whole thing is inherently unsafe anyway, and the idea is to have a very fast access to the underlying data. Consequently, all implementations of mmtkvdb::Storable are unsafe impls.

I plan to add this:

unsafe impl<const N: usize> Storable for [u8; N] {
    type AlignedRef<'a> = &'a Self;
    unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self::AlignedRef<'_> {
        &*bytes.as_ptr().cast()
    }
    /* … */
}

unsafe impl<const N: usize> StorableConstBytesLen for [u8; N] {
    const BYTES_LEN: usize = N;
}

Safety advice is given here, for example:

  • mmtkvdb::EnvBuilder::open_ro:
    • "The environment must be free of corruption on the storage medium."
    • "The file format is platform specific, so endianness and word size must not change across use."
  • mmtkvdb::EnvRo::open_db
    • "If a database exists already, it must have been created with compatible options."

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.