Dangling reference in const context when creating slice from array

I am building a Vec type that works in a const context, with a fixed maximum capacity.
I want to convert the vector to a &'static [T] using constant promotion when I'm finished editing it.

This is what I came up with:

pub struct ConstVec<const CAP: usize, T> {
    arr: [MaybeUninit<T>; CAP],
    len: usize,
}

impl<const CAP: usize, T> ConstVec<CAP, T> {
    pub const fn new() -> Self {
        Self {
            arr: [const { MaybeUninit::uninit() }; CAP],
            len: 0,
        }
    }

    // ... snip ...

    // Problematic function
    pub const fn into_slice(self) -> &'static [T] {
        unsafe {
            let ptr = self.arr.as_ptr() as *const T;
            core::slice::from_raw_parts(ptr, self.len)
        }
    }
}

This code compiles without problems, but when I try to use it:

const fn make_vec<const CAP: usize>() -> ConstVec<CAP, usize> {
    let mut vec = ConstVec::new();
    let mut index = 0;
    while index < CAP {
        vec.push(index);
        index += 1;
    }
    vec
}
// error[E0080]: it is undefined behavior to use this value
const VALUE: &[usize] = make_vector::<10>().into_slice();

I get this error:

error[E0080]: it is undefined behavior to use this value
   --> src/lib.rs:180:5
    |
180 |     const VALUE: &[usize] = make_vector::<10>().into_slice();
    |     ^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (use-after-free)
    |
    = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it
should not be considered undefined behavior.
    = note: the raw bytes of the constant (size: 16, align: 8) {
                ╾────alloc61<imm>─────╼ 0a 00 00 00 00 00 00 00 β”‚ ╾──────╼........
            }

(Playground)

The compiler tells me that there is a dangling reference.
Is it caused by the fact that the underlying array is dropped?
Is there a way to fix this error?

When you take self by value, self is dropped at the end of the call to into_slice, leaving you with a dangling reference to self.arr. Note that const functions and methods can be called both at compile time and at run time, so self doesn't have to be a constant. Thus the expression returned from into_slice cannot be promoted. This becomes more clear when we desugar the attempted constant promotion to { const _PROMOTED = &EXPR; _PROMOTED }:

    pub const fn into_slice(self) -> &'static [T] {
        const _PROMOTED = &unsafe {
            let ptr = self.arr.as_ptr() as *const T;
            core::slice::from_raw_parts(ptr, self.len)
        }; 
        
        _PROMOTED
    }

The first compile error we get is:

error[E0435]: attempt to use a non-constant value in a constant
  --> src/main.rs:34:23
   |
34 |             let ptr = self.arr.as_ptr() as *const T;
   |                       ^^^^ non-constant value

Playground.

You already have a as_slice method, why can't you use that?

1 Like

Constant promotion only happens when you borrow a constant expression. You can't invoke it in any way inside a const fn. The contents of a const fn are not constants; one way to see this is to remember that const fns can be called at run time, so they can't do anything that wouldn't be possible both at run time and at comple time. So, if into_slice() was called at run time, it would have to allocate some memory, and there is (currently) no way to allocate memory that also works at compile time.

In practice, this means that your make_vector().into_slice() can be defined as a macro, but cannot be defined as a const fn.

2 Likes

Thanks for the helpful replies!

Yes you're right the as_slice method works, I can basically use it in place of into_slice:

const SLICE: &[usize] = make_vector::<10>().as_slice()
1 Like