I like to remind, that the AllocRef
trait is by far not set in stone and every method or even generic parameter is gated behind #[feature(allocator_api)]
.
Yes, this is why I thought important to mention it as soon as possible, when things are still in flux
I don't really see, how InlineVec
solves this problem. Using your provided implementation, this could look like this:
I've amended the Poc.
I just added (on top of a few feature toggles):
unsafe impl<T, const N: usize> ContiguousStorage<T> for [mem::MaybeUninit<T>; N] {
type CapacityType = usize;
fn new(size: usize) -> Result<Self, ()> {
if size <= N {
Ok(mem::MaybeUninit::uninit_array())
} else {
Err(())
}
}
fn storage(&self) -> &[mem::MaybeUninit<T>] { &*self }
fn storage_mut(&mut self) -> &mut [mem::MaybeUninit<T>] { &mut *self }
fn try_resize(&mut self, new_size: usize) -> Result<(), ()> {
if new_size <= N {
Ok(())
} else {
Err(())
}
}
}
And instantly I can do:
fn show_off<S: ContiguousStorage<i32>>(storage: S) {
let mut vec = Vec::with_storage(storage);
vec.push(1);
vec.push(2);
println!("{:?}", vec.as_slice());
let element = vec.pop();
println!("{:?}, {:?}", element, vec.get(1));
}
fn main() {
show_off(HeapStorage::new(0).unwrap());
show_off([mem::MaybeUninit::<i32>::uninit(); 2]);
}
Note that the important difference with the Allocator API is that my definition of Vec
never stores a pointer. Instead, anytime it needs a pointer to the storage it asks the storage.
This is the critical difference that allows moving the Vec (and its storage) around without issues.
When retrieving a pointer to the data of InlineVec
, it would also be invalidated after moving the vector. Would you simply say, that dereferencing a pointer is unsafe
, so the user has to care?
How is it different from the current interface? Dereferencing a pointer is always unsafe
.
I like to invert your statement: What is not possible with Storage
, but is possible with AllocRef
when using in collections and smart pointers? (I think nothing, as AllocatorStorage
could be a wrapper around AllocRef
)
I think indeed that Storage
is strictly more generic, and I'll continue hacking on the PoC today to see where it leads me. I just want to avoid eliciting too much and letting everyone down if I overlooked something
I'm not sure what "inline storage" means but with C++'s allocator model, it is possible to have an allocator that pulls all of its storage from a stack-allocated buffer.
A stack-allocator buffer is indeed possible in C++, however this is restricted.
If I build a std::map<std::basic_string<char, stack_allocator>, V>
, what is the size of the stack_allocator
that I need? This will depend on the map.
If instead I build a std::map<inline_string<24>, V>
, then the storage of each string is contained within the string itself.
Inline storage is more flexible than stack storage.
Which is why I strongly believe that the C++ allocator model is NOT necessarily the right abstraction, here, since it doesn't handle storing the state within the allocator itself.
However, on the topic of learning from C++ I find it absolutely bewildering that the approach would publicly denote the type which it will internally allocate.
Indeed.
I touched briefly on it when mentioning the storage of heterogeneous. I think the storage trait should be independent of the elements, I just haven't found a good (easy to use) way to express it yet.
Do you want to add the full topic there or do you want me to open an issue with a short description and a link?
I'm not familiar with the way you prefer to work.
The idea is still very rough. I'm relatively confident it can work well for contiguous storage, but quite less so for non-contiguous storage such as needed by BTreeMap.
I'll let you judge the right time (maturity?) to open the issue.