Is there a reason why Vec::len is not a const fn?

https://doc.rust-lang.org/src/alloc/vec.rs.html#1365-1367

Technically Vec::len() could be a const fn, is there any reason why that's not the case or was it just overlooked? Thanks.

The reason is that Vec cannot be constant except for empty vec.

2 Likes

See the const-eval issue for heap allocations in constants

1 Like

Yes, but that's not what I need it for. but conversions into other custom types could be const:

#[repr(C)]
pub struct BlahVec {
    ptr: *const Blah,
    len: usize,
    cap: usize,
    destructor: BlahDestructor,
}

#[repr(C, u8)]
pub enum BlahDestructor {
    DefaultRust,
    NoDestructor,
    External(extern "C" fn(*mut BlahVecDestructor)),
}

impl BlahVec {

    #[inline(always)]
    pub const fn new() -> Self {
        Self::from_vec(Vec::<Blah>::new())
    }

    //  I want this conversion fn to be const, that's why
    #[inline(always)]
    pub const fn from_vec(v: Vec<Blah>) -> BlahVec {
        use std::mem::ManuallyDrop;

        // note: &[T]::as_ref() is a const fn, Vec<T>::as_ptr() is not?

        let ptr = (&v[..]).as_ptr();
        let len = (&v[..]).len();
        let cap =  (&v[..]).capacity();

        let _ = ManuallyDrop::new(v);

        BlahVec {
            ptr,
            len,
            cap,
            destructor: BlahVecDestructor::DefaultRust,
        }
    }
}

impl Drop for BlahVec {
    fn drop(&mut self) {
        match self.destructor {
            BlahVecDestructor::DefaultRust => { let _ = unsafe { Vec::from_raw_parts(self.ptr as *mut Blah, self.len, self.cap) }; },
            BlahVecDestructor::NoDestructor => { },
            BlahVecDestructor::External(f) => { f(self); }
        }
    }
}

I'm then creating the Vec from a &'static [u8], so the len() is non-zero and also known at compile time. Yes I know this is all unsafe, but I'm not using dynamic allocation.

So you might want to use ArrayVec or the likes, which uses stack as storage.

Well I've solved it now by making two separate functions, one for new() and one for new_nonconst():

impl BlahVec {
    #[inline(always)]
    pub fn new() -> BlahVec  {
        use std::mem::ManuallyDrop;
        let v = Vec::new();
        let md = ManuallyDrop::new(v);

        BlahVec  {
            ptr: md.as_ptr(),
            len: 0,
            cap: 0,
            destructor: $destructor_name::DefaultRust,
        }
    }
}

I can't use ArrayVec or similar because I also need to handle pointers to memory not managed by Rust (i.e. allocated by a C++ std::vector) as well as pointers into static memory (i.e. &'static [T]) as well as regular Rust Vec<T> in one type with as little conversion code as possible.

I can set the ptr and cap to 0, but I can't get the .get_ptr() in a const fn. Right now I've made everything non-const and hope that the optimizer catches it, it works but it's not a great solution.

Unless you're putting the result into a const context (i.e. you have const SOMETHING or static SOMETHING at the use site), const has no impact on the pre-evaluation of the value. If it's inlined to a precomputed value, that's on the optimizer to do, even if you mark it const.

(IOW, const does not mean "constant evaluate this if all inputs are constant", it means "this is constant evaluable, only such that it can be put in a constant compiletime value".)

There are vague murmurings of a future const {} block which would force constant evaluation of its contents (roughly by making it a const context) even in non-const contexts.

4 Likes

They're a little further along than that :stuck_out_tongue:

https://rust-lang.github.io/rfcs/2920-inline-const.html

5 Likes