For context, I implemented a (hopefully) safe vector backed up by a static array as a functional block for a bigger project, but as you may imagine, popping or pushing values can fail when it's either empty or full.
I chose to use assert!s to sanitize these calls instead of creating try_...() methods since the code is dense with such interactions and filling it with unwrap() didn't look useful to me, however without bugs even these checks would be unnecessary.
Should I introduce fallible methods and maybe a newtype called PanicVec or something?
Thanks for you time.
Can you show your current interface? I don't quite understand your question, and it would help. For example std::vec::Vec::<T>::pop returns Option<T>. What does your implementation return, and how do you want to use it?
Your new function is UB i think. You can't do MaybeUninit::uninit().assume_init(). there are a lot of examples that even for integers this is UB. You should look at MaybeUninit::uninit_array() which is nightly but you can just copy the implementation as it is really short.
Regarding your original question: Most of your methods check for valid state and are returning Options. So what is special about pop()? Returning an Option would be very consistent with the rest of your code.
However, if you really would like to provide a shortcut, so a caller can avoid unwrap() at his own risk, maybe a variant unchecked_pop() which is marked unsafe will do fine?
You can, in the special case that all-bits-uninitialized is a valid state of the contained type. This pattern was explicitly recommended in the docs until recently.
We haven’t been shown any manual Drop implementation, but the automatic drop glue here shouldn’t be a problem— MaybeUninit doesn’t call drop() on its contents because it doesn’t do any tracking about whether or not it contains initialized data.
The assert is entirely superfluous here because the indexing operator will bounds-check and panic on its own.
As for the original question: it's ok to use assert! to ensure safety. It always runs (in release mode too), and guarantees to unwind or abort when the assertion fails.
From performance perspective, it's almost always better to use Option or Result and not use any panics. This is because a possibility of a panic can prevent optimizer from moving or changing the code, since it's a major side effect. Regular function return is often cheaper and doesn't prevent optimizations as much.
I thank everyone for your good catches, I've been rather careless in fixing this code before showing it, mainly because it was not meant to be reviewed in that sense, however I invite someone to double check before claiming errors.
Just to minimize the amount of unsafe code that you have to audit and get right, consider writing methods that return &[T] and &mut [T] which you then use internally for anything that doesn't require changing len. You could also consider Derefing to a slice (like Vec does) so that you don't have to manually forward to all of the various slice methods you're interested in: