Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions content/2025-08-20-heapless-091.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
+++
title = "Heapless v0.9.1 has been released!"
date = 2025-08-20
draft = false
in_search_index = true
template = "page.html"
+++

Almost 2 years after the last release, the [heapless](https://github.com/rust-embedded/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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the nitpick but could we please keep all the lines under 100 chars and break lines on the word boundary based on this limit rather than for starting a new sentence (in the same paragraph)? 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing entries don't do that. My editor (helix) handles word wrapping very well, I don't really want to do this manually and would rather focus on the content than the formatting. Markdown and Zola are here to handle the formatting.


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](https://github.com/rust-embedded/heapless/blob/main/CHANGELOG.md). Here are some of the major changes that can improve your usage of the library.

<!-- more -->

# 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`:

```rust
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:

```rust
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](https://doc.rust-lang.org/reference/type-coercions.html#r-coerce.unsize)), 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:

```rust
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` has `VecView`
- `String` has `StringView`
- `Deque` has `DequeView`
- `LinearMap` has `LinearMapView`
- `HistoryBuf` has `HistoryBufView`
- `BinaryHeap` has `BinaryHeapView`
- `mpmc::Queue` has `mpmc::QueueView`
- `spsc::Queue` has `spsc::QueueView`
(and now, the producer and consumer structs don't carry the const-generic)
- `SortedLinkedList` has `SortedLinkedListView`

`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.

[@zeenix]: https://github.com/zeenix
[@sgued]: https://github.com/sgued
[@GnomedDev]: https://github.com/GnomedDev
Loading