Typestate-customizable bump allocator
Find a file
2022-01-07 14:59:02 -05:00
.github/workflows Update rust.yml 2021-09-19 20:55:01 -04:00
src Rework buffers 2022-01-07 14:59:02 -05:00
.gitignore Initial Commit 2021-09-19 17:56:02 -04:00
Cargo.toml Initial Commit 2021-09-19 17:56:02 -04:00
LICENSE-APACHE Initial Commit 2021-09-19 17:56:02 -04:00
LICENSE-MIT Initial Commit 2021-09-19 17:56:02 -04:00
README.md Merge pull request #4 from Cyborus04/build_tag 2021-09-19 21:04:22 -04:00

Rust

fist_bump

A customizable bump allocator.

Note that this crate currently requires nightly rust, as it uses the allocator API.

Usage

Add the following to your Cargo.toml:

[dependencies]
fist_bump = "0.1"

FistBump doesn't directly implement Allocator, however &FistBump does, so that anything that uses it can only live as long as it does.

Anything that is generic over an allocator can be used in a FistBump, such as Vec<T> or Box<T>

Example:

let fistbump = FistBump::<OneUse<ArrayBuffer<64>>>::new();
let mut vec = Vec::new_in(&fistbump);
vec.extend_from_slice(b"Hello");
vec.extend_from_slice(b", world!");
assert_eq!(&vec, b"Hello, world!");

let boxed = Box::new_in((1..=100).sum(), &fistbump);
assert_eq!(boxed, 5050);

(currently this example isn't tested because it triggers an ICE)

Customization

FistBump uses generics to let you choose what functionality you want from it.

Buffers

A fistbump is always backed by a buffer of bytes, which is where it stores its contents.

ArrayBuffer

ArrayBuffer lives on the stack, and has a constant size.

It is generic over its size, and is basically a [u8; N].

#![feature(allocator_api)]
use fist_bump::{FistBump, OneUse, ArrayBuffer};

let fistbump = FistBump::<OneUse<ArrayBuffer<16>>>::new();
// You should prefer using `with_capacity_in` rather than `new_in`
// as re-allocating repeatedly wastes a lot of space
let mut vec = Vec::with_capacity_in(4, &fistbump);
vec.push(15u32);
vec.push(15493u32);
vec.push(62u32);
vec.push(150000u32);

DynamicBuffer

DynamicBuffer lives inside another allocator, and can be resized.

Its size is determined at runtime, and is basically a Vec<u8>

#![feature(allocator_api)]
use fist_bump::{FistBump, OneUse, DynamicBuffer};

let mut fistbump = FistBump::<OneUse<DynamicBuffer>>::with_size(16);
let mut vec1 = Vec::with_capacity_in(4, &fistbump);
vec1.push(42);
vec1.push(3141);
vec1.push(151);
vec1.push(11);

// Need to drop the vec, see below
drop(vec1);
fistbump.resize(24);

let mut vec2 = Vec::with_capacity_in(8, &fistbump);
vec2.extend_from_slice(b"rustlang");
assert_eq!(vec2, b"rustlang");

The buffer cannot be resized while there are outstanding allocations, since the pointers into it may be invalidated.

# #![feature(allocator_api)]
# use fist_bump::{FistBump, OneUse, DynamicBuffer};
let mut fistbump = FistBump::<OneUse<DynamicBuffer>>::with_size(4);
let mut vec = Vec::with_capacity_in(4, &fistbump);
vec.extend_from_slice(b"heyo");
fistbump.resize(8);
assert_eq!(vec, b"heyo");

Reusability

Reusable and OneUse are wrappers around a buffer that indicate whether the buffer can be used more than once or not.

[OneUse]

[Reusable]

Takes a mutable reference to a buffer, allowing it to be used more than once. Has to take a mutable reference, or else multiple FistBumps could use it at once, overwriting each other.

#![feature(allocator_api)]
use fist_bump::{FistBump, ArrayBuffer};

let mut buffer = ArrayBuffer::<16>::new();
{
    let fistbump_a = FistBump::in_buffer(&mut buffer);
    // do some stuff
}
// At this point, `fistbump_a` has gone out of scope, and so the
// buffer can now be re-used.
{
    let fistbump_b = FistBump::in_buffer(&mut buffer);
    // do some other stuff
}

It's better to use a single Reusable, rather than repeatedly creating OneUses.

For example, instead of doing this:

for i in 0..1000 {
    let fistbump = FistBump::<OneUse<ArrayBuffer<64>>>::new();
    // do stuff with it...
}

Do this:

let mut buffer = ArrayBuffer::<64>::new();
for i in 0..1000 {
    let fistbump = FistBump::in_buffer(&mut buffer);
    // do stuff with it...
}

Thread Safety

By default, FistBump is not thread safe. However, you can enable thread-safety by using the ThreadSafe flag, i.e. FistBump<_, ThreadSafe>. Additionally, you can explicitly set it to being thread-local with FistBump<_, ThreadLocal>.

If you don't need to use it across threads, leave it set to ThreadLocal, as that is likely to be more efficient.

Also note that even if it is set to ThreadLocal, a FistBump can still be sent across threads, just not shared. This is okay because sending it requires there be no references to it, and thus no allocations.

Usage in statics

When using a FistBump in a static, several restrictions apply:

  • It must be OneUse.
  • It must use an ArrayBuffer.
  • It must be ThreadSafe.

These restrictions are represented by the type FistBump<OneUse<ArrayBuffer<_>>, ThreadSafe>

License

This project is licensed under either of

at your option.