-
Notifications
You must be signed in to change notification settings - Fork 36
Heapless release 091 #200
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
Merged
Merged
Heapless release 091 #200
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||
|
||
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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)? 🙏
There was a problem hiding this comment.
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.