Heapless v0.9.1 has been released!
2025-08-20Almost 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:
Vec
hasVecView
String
hasStringView
Deque
hasDequeView
LinearMap
hasLinearMapView
HistoryBuf
hasHistoryBufView
BinaryHeap
hasBinaryHeapView
mpmc::Queue
hasmpmc::QueueView
spsc::Queue
hasspsc::QueueView
(and now, the producer and consumer structs don't carry the const-generic)SortedLinkedList
hasSortedLinkedListView
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.