Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Support a fixed-dynamic-capacity Vec #353

Open
kupiakos opened this issue Mar 2, 2023 · 1 comment
Open

Feature: Support a fixed-dynamic-capacity Vec #353

kupiakos opened this issue Mar 2, 2023 · 1 comment

Comments

@kupiakos
Copy link

kupiakos commented Mar 2, 2023

So, a dynamically sized Vec, but still with a max capacity. This would remain heapless, as you can construct the slice on the stack or static and pass it in. There are some particular advantages to this approach in an embedded environment, the biggest of which being the lack of monomorphization required to support multiple capacities. A SliceVec<'a, T> with dynamic length N can be produced via a method on &'a mut Vec<T, N>. It can also be safely produced from a &'a mut [MaybeUninit<T>] and &'a mut [T], since we never write new uninit data.

A SliceVec can also be used on external buffers, such as keeping track of how many bytes of an uninit buffer have been written to and provide a safe method to retrieve the initialized bytes. This is particularly useful for zero-copy deserialization.

If I were send a PR to add this, would it be accepted? This is a blocker for my team switching to heapless off of FixedVec, which has less support and fewer types to work with.

@kupiakos kupiakos changed the title Feature: Support SliceVec, backed by a &mut [MaybeUninit<T>] instead of [MaybeUninit<T>; N] Feature: Support SliceVec<'a, T>, backed by a &mut [MaybeUninit<T>] instead of [MaybeUninit<T>; N] Mar 2, 2023
@kupiakos kupiakos changed the title Feature: Support SliceVec<'a, T>, backed by a &mut [MaybeUninit<T>] instead of [MaybeUninit<T>; N] Feature: Support SliceVec<'a, T>, backed by a &'a mut [MaybeUninit<T>] instead of [MaybeUninit<T>; N] Mar 2, 2023
@kupiakos kupiakos changed the title Feature: Support SliceVec<'a, T>, backed by a &'a mut [MaybeUninit<T>] instead of [MaybeUninit<T>; N] Feature: Support a fixed-dynamic-capacity Vec Mar 3, 2023
@kupiakos
Copy link
Author

kupiakos commented Mar 3, 2023

So, after some deliberation, my initial proposal isn't quite right. You cannot simply produce a SliceVec<'a, T> by borrowing on a &'a mut Vec<T, N>, since the length isn't borrowed. I'm considering this shape of function:

/// Read up to `N` bytes from some response into `out`.
fn read_from_response<const N: usize>(&mut self, out: &mut heapless::Vec<u8, N>) -> Result<(), Err>;

We want this to be able to work safely with different N, but avoid monomorphization for different N. We can do that with an inner function and the right inlining, but it'd be much simpler for us to express this as a type.

However, I also want to be able to work with an externally provided buffer, as parts of our system design require it. Right now we're just unsafe and I'm trying to reduce our amount of unsafety with the right primitives. It turns out safely filling in an uninitialized buffer is perfectly represented by a Vec that borrows from an &'a mut [MaybeUninit<T>] instead of storing its own array inline.

So, I want a Vec that borrows from an external buffer, but I also want one that can be easily borrowed from a Vec<T, N>. These are at odds.

So, the best design I have is two separate types, and I'd like to hear your thoughts. Both avoid monomorphization on N. I have a basic demo playground here.

  • DynVec<T>, which is a DST that can be unsized from a reference to Vec<T, N>. It essentially turns the N of a monomorphization into an extra runtime parameter, a la &[T; N] to &[T].
  • BorrowVec<'a, T>, which is distinct from Vec<T, N> and writes to an externally provided &'a mut [MaybeUninit<T>] (or &'a mut [T]. You could convert a &'a Vec<T, N> to a BorrowVec<'a, T> by borrowing the spare capacity, but it wouldn't be able to drop appended items. This is always true for BorrowVec<'a, T> - appending to one will never cause an additional drop. This is essentially FixedVec.

Our paradigms pretty much require the latter, but the former seems like an overall better fit for the library. I'd love if I could have both in here and spruce up some of the utility functions, but I don't want to put in the effort in to upstream if the maintenance/usability burden is excessive on your end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants