#allocator-api #global-allocator #allocator #stalloc

no-std stalloc

Stalloc is a fast first-fit memory allocator that you can use to quickly speed up your Rust programs

10 unstable releases (3 breaking)

Uses new Rust 2024

0.5.3 Apr 6, 2025
0.5.2 Apr 4, 2025
0.4.2 Mar 31, 2025
0.3.0 Mar 28, 2025
0.2.1 Mar 28, 2025

#90 in Memory management

Download history 262/week @ 2025-03-24 558/week @ 2025-03-31 102/week @ 2025-04-07

922 downloads per month

MIT license

67KB
1.5K SLoC

Stalloc (Stack + alloc) is a fast first-fit memory allocator. From my benchmarking, it can be over 3x as fast as the default OS allocator! This is because all memory is allocated from the stack, which allows it to avoid all OS overhead. Since it doesn't rely on the OS (aside from SyncStalloc), this library is no_std compatible.

Note that Stalloc uses a fixed amount of memory. If it ever runs out, it could result in your program crashing immediately. Stalloc is especially good for programs that make lots of small allocations.

Stalloc is extremely memory-efficient. Within a 32-byte "heap", you can allocate eight Box<u32>s, free them, then allocate four Box<u64>s, free them, and then allocate two Box<u128>s. This can be especially useful if you're working in a very memory-constrained environment and you need a static upper limit on your application's memory usage.

There are three main ways to use this library:

With the allocator API

#![feature(allocator_api)]

let alloc = Stalloc::<200, 4>::new(); // 200 blocks, 4 bytes each
let mut v = Vec::new_in(&alloc);
v.push(25);

// Since the allocator is about to get dropped anyway, no need to call the destructor of `v`.
mem::forget(v);
// `alloc` gets dropped at the end of the scope

With the unsafe APIs

let alloc = Stalloc::<80, 8>::new();

let alignment = 1; // measured in block size, so 8 bytes
let ptr = unsafe { alloc.allocate_blocks(80, alignment) }.unwrap();
assert!(alloc.is_oom());
// do stuff with your new allocation

// later...
unsafe {
	alloc.deallocate_blocks(ptr, 80);
}

As a global allocator

#[global_allocator]
static GLOBAL: SyncStalloc<1000, 4> = SyncStalloc::new();

fn main() {
	// allocations and stuff
	let v = vec![1, 2, 3, 4, 5];

	// we can check on the allocator state
	println!("{GLOBAL:?}");
}

If your program is single-threaded, you can avoid a little bit of overhead by using UnsafeStalloc, which isn't thread-safe.

#[global_allocator]
static GLOBAL: UnsafeStalloc<1000, 4> = unsafe { UnsafeStalloc::new() };

To avoid the risk of OOM, you can create an allocator chain, which uses the next one as a fallback if something has gone wrong:

// Create an allocator chain, where we try to use the fast `SyncStalloc`, but fall back to `System`.
#[global_allocator]
static GLOBAL: AllocChain<SyncStalloc<1000, 8>, System> = SyncStalloc::new().chain(&System);

When you create a Stallocator, you configure it with two numbers: L is the number of blocks, and B is the size of each block in bytes. The total size of this type comes out to L * B + 4 bytes, of which L * B can be used (4 bytes are needed to hold some metadata). The buffer is automatically aligned to B. If you want it to be more aligned than that, you can create a wrapper like this:

#[repr(align(16))] // aligned to 16 bytes
struct MoreAlignedStalloc(Stalloc<8, 4>); // eight blocks of four bytes each

To use this library with no-std, add the following to your Cargo.toml:

[dependencies]
stalloc = {version = <latest>, default-features = false}

To use this library with the allocator API, add the following instead:

[dependencies]
stalloc = {version = <latest>, features = ["allocator-api"]}

No runtime deps

Features