Perhaps one way you could design this (which would fit in well with custom allocators) is through adding another type parameter to Vec:
pub struct Vec<T, Allocator> { /* stuff */ }.
The Allocator trait would have some way to perform an allocation:
fn allocate(size, alignment) -> Result<*mut u8, Error>.
For an infallible allocator, you simply set Error to enum Void {} or ! (i.e. non-instantiatable type).
Then, you avoid the duplication of having to have fallible & infallible versions of Vec, by making every method look somewhat like this:
fn push(&mut self, item: T) -> Result<(), Error> where the Error is the same type as above. Of course, if you’re using an infallible allocator, this Result essentially is ().
Now, obviously, this doesn’t actually work, as Rust doesn’t appreciate that Result<(), !> == (). As a stopgap, you could make a wrapper type that unwraps all of these results, but then we’re back to the duplication thing again. However, if Rust ever does gain the ability to equate Result<(), !> to (), then this model may prove helpful, as you can get rid of the duplicated wrapper version.