Prevent vector from being dropped

I have some code that looks something like this (simplified example):

pub fn example() -> *mut ManuallyDrop<Vec<T>> {
    // Allocate Vec wrapped in a ManuallyDrop
    let mut temp = ManuallyDrop::new(Vec::new());
    // Return pointer to Vec
    ptr::addr_of_mut!(temp);
}

I thought wrapping it in a ManuallyDrop would prevent the Vec from being dropped when this function returns, but it looks like it's still being dropped anyway. When I dereference the pointer later, the values have been overwritten. Why is this the case? Is there any way to prevent the Vec from being dropped?

ManuallyDrop just keeps Drop::drop from running. You still have a local variable that will go out of scope when the function returns, and using pointers doesn't change that.

Can you say why you're doing this? The normal thing is to return the Vec<T> by value, so to give any useful advice you'll have to explain why you're not just doing that.

4 Likes

I simplified the code in my example to boil it down to the particular issue I was asking about.

In my actual code, I have a struct that contains a pointer to a Vec. I'm using pointers because the Vec is allocated using a custom heap allocator, and and the struct may be allocated before that allocator is initialized, so I initially set the pointer to null, then set it to point to the Vec once it's allocated.

Maybe you can use MaybeUninit for this instead of a raw pointer?

Or Option and references?

2 Likes

Option doesn't work because Vec doesn't implement the Copy trait, but I can try MaybeUninit.
Thank you both for the suggestion! I'm still new to Rust, so I'm not familiar with all the features available in the standard libraries.

I'm not sure exactly what are you trying to do, but if you don't (yet) feel confident with the rules of borrowing and the language, I highly recommend you to stay away from unsafe code (for now). It's really easy to shoot yourself in the foot with it, and trying to get around compiler errors with it is a bad idea most of the time. When the compiler yells at you, it usually has a reason to do so.

4 Likes

Why do you have a pointer to a Vec? A Vec is just a pointer and two usizes, so it's typically not something you allocate. And an empty Vec doesn't allocate at all.

11 Likes

Option doesn't require Copy types.

Empty Vec has a null dummy data pointer internally already, it's not something you need to micromanage.

I suspect you're trying to use a C-specific approach that needlessly makes Rust code unsafe and overcomplicated.

7 Likes
Nitpick

An empty Vec actually has a dangling pointer, not a null one -- the pointer, after a few wrappers, is a NonNull<_> -- which it uses as a niche so that Option<Vec<T>> has the same size

Even Deeper

Well the implementation has now added a capacity niche too, which is bigger than the pointer one, but the pointer still isn't null.

5 Likes

You're probably right, most of my experience is in C.

Ok, I think my belief that Option requires Copy types was based off my misinterpretation of an error message.

I don't see how the fact that an empty Vec has a null data pointer (or dangling pointer) matters here. I still can't allocate the Vec at all until the allocator is initialized, so I still have to have some kind of placeholder value until then, right?

An empty Vec doesn't call its allocator at all.

Indeed, if it tried to call the allocator that'd be UB because https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html#tymethod.alloc is UB if you call it with a zero-sized Layout.

Vec<T, Global> and Vec<T, WhateverYouAreUsing> can have different layouts. It's still not clear how you're (trying to) get from your OP to your data structure, but transmutation is probably the wrong answer.

Maybe you want a builder type that produces the desired data structure when handed the initialized allocator. I'm shooting in the dark though...

You've boiled away too much and are probably asking XY questions from a shaky foundation. The last comment was the first mention of new_in for example.

Perhaps share your data structure and how you imagined it being constructed.

Sure. I mentioned that the vectors use a custom allocator, I didn't explicitly mention new_in(). Sorry for the confusion.

The struct contains an allocator and a vector:

pub struct example {
    alloc: Option<&'static Allocator>,
    vec: Vec<u64, Allocator>,
}

impl example {
    pub fn new() -> Self {
        Self {
            alloc: None,
            vec:  // what do I initialize it to??
        }
    }

    pub fn init(&mut self, a: &'static Allocator) {
        self.alloc = Some(a);
        // Allocate vector in a
    }
}

I'm having trouble figuring out what the default value for vec should be before the allocator is initialized.

It's very fortunate that a Vec created with zero capacity will not allocate, and this is guaranteed in the doc. It solves your problem and is relied on by many people for similar reasons.

This may be a little obscure if you're just learning Rust, but good to know: Default is implemented for Vec by creating a zero capacity Vec, which is useful for calling mem::take without worrying about an unnecessary allocation. Edit: As @quinedot pointed out, Default isn't implemented for a Vec with a non-global allocator, but you can use Vec::new_in to create the zero capacity Vec, and use mem::replace instead of mem::take.

Basically, don't construct the Vec until handed an allocator.

If you unsafe build this version somehow, where the allocator doesn't actually exist until init is called...

pub struct example {
    alloc: Option<&'static Allocator>,
    vec: Vec<u64, /* &'static */ Allocator>,
}

...and your implementation of, say, push doesn't check that it is initialized...

// SAFETY: you must have called `.init()` before calling this
unsafe fn push(&mut self, item: u64) {
    // Maybe self.alloc.is_none() ... eh whatever, yolo!
    self.vec.push(item);
}

...it either has to be unsafe to call, or the API is unsound.


Alternatively, use OnceLock or similar to construct a global allocator when the Example is created.


Apparently the OP needs Vec<u64, NotGlobal>, which does not implement Default.

2 Likes

Put both of them in the same Option (you can use a tuple or make another struct type). There's option.get_or_insert_with(…).

This will also force you to handle cases when the Vec can't be used yet.

2 Likes

As always, just add another layer of indirection. In the short term, something like this will tackle your problem:

pub struct RiskyVec<T, A: Allocator>(Option<Vec<T, A>>);

impl<T, A: Allocator> RiskyVec<T, A> {
    pub fn new() -> Self {
        RiskyVec(None)
    }
    pub fn init(this: &mut Self, allocator: A) {
        this.0 = Some(Vec::new_in(allocator));
    }
}
impl<T, A: Allocator> std::ops::Deref for RiskyVec<T, A> {
    type Target = Vec<T, A>;
    fn deref(&self) -> &Self::Target {
        self.0.as_ref().expect("init should have been called first")
    }
}
impl<T, A: Allocator> std::ops::DerefMut for RiskyVec<T, A> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.0.as_mut().expect("init should have been called first")
    }
}

In the long run, it would be more sustainable to eliminate the use of RiskyVec. You can try to rework your program structure to "bubble up" the option that holds a vector so there will be no surprise of an unexpected panic.

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.