Heapless v0.9.1 has been released!

Almost 2 years after the last release, the heapless crate has a new release. The first attempt at a 0.9.0 release was yanked due to including more breaking changes than intended. This has been fixed, and 0.9.1 has been released today.

Compared to 0.8.0, the 0.9.1 release contains a bunch of small everyday improvements and bugfixes. Most users of the library should be able to adapt with minimal changes. For more information, you can check out the changelog. Here are some of the major changes that can improve your usage of the library.

The View types

One of the main constraints when working with heapless types is that they all have a const generic. In a lot of situations, these can now be removed thanks to the View types.

A lot of embedded firmware will allocate a couple of buffers and pass them around to save on memory. To make it easy to change the size of the buffers, functions will carry along these const generics:

use heapless::Vec;
struct App{
}

impl App {
     pub fn handle_request<const N: usize, const M: usize>(input: &mut Vec<u8, N>, output: &mut Vec<u8, M>) -> Result<(), Error> {
     }
}

The new View variants of the types enable you to remove the const generics while still keeping the same functionality:

use heapless::VecView;
struct App{
}

impl App {
     pub fn handle_request(input: &mut VecView<u8>, output: &mut VecView<u8>) -> Result<(), Error> {
     }
}

Call sites of handle_request will be able to stay the same. The function will continue to accept &mut Vec<u8, N>.

So what's the difference between VecView and Vec?

There are almost none, both are aliases of the same underlying type VecInner. The only limitation of VecView compared to Vec is that VecView is !Sized. This means that you cannot perform anything that would require the compiler to know the size of the VecView at compile-time. You will always need to manipulate VecView through pointer indirection (generally a reference). This means you can't just create a VecView out of thin air. The VecView is always a runtime "View" of an existing Vec.

So how can we obtain a VecView ? It's pretty simple: Vec can be coerced into a VecView. Coercion (in this case Unsized coercion), is a way the compiler can transform one type into another implicitly. In this case, the compiler is capable of converting pointers to a Vec (&Vec<T, N>, &mut Vec<T, N>, Box<Vec<T, N>> etc...) to pointers to a VecView (&VecView<T>, &mut VecView<T>, Box<VecView<T>> etc...), so you can use a reference to a Vec when a reference to a VecView is expected:

use heapless::{VecView, Vec};
struct App{
}

impl App {
     pub fn handle_request(input: &mut VecView<u8>, output: &mut Vec<u8>) -> Result<(), Error> {
     }
}

let mut request: Vec<u8, 256> = Vec::new();
let mut reply: Vec<u8, 256> = Vec::new();

app.handle_request(&mut request, &mut reply).unwrap();

If you prefer things to be explicit, the View variants of types (Vec is not the only data structure having View variants) can be obtained through vec.as_view() or through vec.as_mut_view().

The pointer to the VecView is the size of 2 usize: one for the address of the underlying Vec, and one for the capacity of the underlying Vec. This is exactly like slices. VecView<T> is to Vec<T, N> what a slice [T] is to an array [T; N]. Unless you need to store data on the stack, most often you will pass around &mut [T] rather than &mut [T; N], because it's simpler. The same applies to VecView. Wherever you use &mut Vec<T, N>, you can instead use &mut VecView<T>.

The View types are not available just for Vec. There are View versions of a lot of heapless types:

IndexMap and IndexSet are the two remaining structures that don't have a View type available. We hope to be able to use it in the future.

Benefits of the view types

The benefits are multiple:

Better compatibility with dyn Traits

If a trait has a function that takes a generic, it is not dyn compatible. By removing the const generic, the View types can make dyn Trait pass around data structures without having to hard-code a single size of buffer in the trait definition.

Better binary size and compile times

When you use const-generics, the compiler needs to compile a new version of the function for each value of the const-generic. Removing the const generic means cutting down on duplicated functions that are all almost the same, which improves both compile time and the size of the resulting binary.

Better ergonomics

The View types can remove a ton of excess noise from the generics.

The LenType optimization

Most often, buffers in embedded applications will not contain a huge number of items. Until 0.9.1 the capacity of a heapless data structure was almost always stored as a usize, which can often encode much more values than necessary.

In 0.9.1, data structures now have a new optional generic parameter called LenT. This type accepts u8, u16, u32, and usize, and defaults to usize to keep typical uses of the library, simple.

If you are seriously constrained by memory, a Vec<T, 28> (equivalent to Vec<T, 28, usize>) can become a Vec<T, 28, u8>, saving up to 7 bytes per Vec. This is not much, but in very small microcontrollers, it can make the difference between a program that uses all the memory available and one that just fits.

Contributors

This release was made possible by @Zeenix joining the embedded working group as part of the libs team to help maintain heapless and convincing @sgued to do the same.

The View types were a contributions from @sgued, and the LenType were contributed by @GnomedDev. In total 38 contributors participated in all the other improvements to the crate and helped with maintainance.