An open API service indexing awesome lists of open source software.

https://github.com/virxec/combo_vec

A library for creating a "combo stack array-heap vector", or simply a re-sizable array
https://github.com/virxec/combo_vec

data-structures rust stack

Last synced: 6 months ago
JSON representation

A library for creating a "combo stack array-heap vector", or simply a re-sizable array

Awesome Lists containing this project

README

          

# combo_vec

[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)

`ComboVec` is for creating a "combo stack array-heap vector", or simply a resizable array with a vector for extra allocations.

Not only that, but this library has `ReArr` if you just want the resizable array part!

Create a new `ComboVec` with the `combo_vec!` macro and a new `ReArr` with the `re_arr!` macro.

This works by allocating an array of `T` on the stack, and then using a Vec on the heap for overflow.

The stack-allocated array is always used to store the first `N` elements, even when the array is resized.

_No_ `Default`, `Copy`, or `Clone` traits are required for `T` at all;
but if T does implement any of them, then `ComboVec` and `ReArr` will also implement them.
This also applied to `PartialEq`, `PartialOrd`, `Eq`, `Ord`, `Hash`, `Debug`, and `Display`.

## Why use ComboVec

This is mostly used for when you know the maximum number of elements that will be stored 99% if the time, but don't want to cause errors in the last 1% and also won't want to give up on the performance of using the stack instead of the heap most of the time.

I've gotten performance bumps with `ComboVec` over the similar type SmallVec (both with and without it's `union` feature.)

In a test of pushing 2048 (pre-allocated) elements, almost a 54% performance increase is shown:

- `ComboVec`: 4.54 µs
- `SmallVec`: 9.33 µs

The `combo_vec!` macro is very nice and convenient to use even in const contexts.

```rust
use combo_vec::{combo_vec, ComboVec};

const SOME_ITEMS: ComboVec = combo_vec![1, 2, 3];
const MANY_ITEMS: ComboVec = combo_vec![5; 90];
const EXTRA_ITEMS: ComboVec<&str, 5> = combo_vec!["Hello", "world", "!"; None, None];

// Infer the type and size of the ComboVec
const NO_STACK_F32: ComboVec = combo_vec![];

// No const-initialization is needed to create a ComboVec with allocated elements on the stack
use std::collections::HashMap;
const EMPTY_HASHMAP_ALLOC: ComboVec, 3> = combo_vec![];

// Creating a new ComboVec at compile time and doing this does have performance benefits
let my_combo_vec = EMPTY_HASHMAP_ALLOC;
```

`ComboVec` also implements many methods that are exclusive to `Vec` such as `extend`, `truncate`, `push`, `join` etc.

## Why use ReArr

In a test of pushing 2048 (pre-allocated) elements, it ties for performance with `ArrayVec`:

- `ReArr`: 4.07 µs
- `ArrayVec`: 4.00 µs

The `re_arr!` macro is very nice and convenient to use even in const contexts.

```rust
use combo_vec::{re_arr, ReArr};

const SOME_ITEMS: ReArr = re_arr![1, 2, 3];
const MANY_ITEMS: ReArr = re_arr![5; 90];
const EXTRA_ITEMS: ReArr<&str, 5> = re_arr!["Hello", "world", "!"; None, None];

// Infer the type and size of the ReArr
const NO_STACK_F32: ReArr = re_arr![];

// No const-initialization is needed to create a ComboVec with allocated elements on the stack
use std::collections::HashMap;
const EMPTY_HASHMAP_ALLOC: ReArr, 3> = re_arr![];

// Creating a new ReArr at compile time and doing this does have performance benefits
let my_re_arr = EMPTY_HASHMAP_ALLOC;
```

`ReArr` also implements many methods that are exclusive to `Vec` such as `extend`, `truncate`, `push`, `join` etc.

## Examples

A quick look at a basic example and some methods that are available:

```rust
use combo_vec::combo_vec;

let mut combo_vec = combo_vec![1, 2, 3];
// Allocate an extra element on the heap
combo_vec.push(4);
// Truncate to a length of 2
combo_vec.truncate(2);
// Fill the last element on the stack, then allocate the next items on the heap
combo_vec.extend([3, 4, 5]);
```

### Allocating empty memory on the stack

You can allocate memory on the stack for later use without settings values to them!

No Copy or Default traits required.

```rust
use combo_vec::{ComboVec, ReArr};

// Allocate a new space to store 17 elements on the stack.
let empty_combo_vec = ComboVec::::new();
let empty_re_arr = ReArr::::new();
```

### Allocating memory on the stack in const contexts

The main benefit of using the `combo_vec!`/`re_arr!` macros is that everything it does can be used in const contexts.

This allows you to allocate a ComboVec at the start of your program in a Mutex or RwLock, and have minimal runtime overhead.

```rust
use combo_vec::{combo_vec, ComboVec, re_arr, ReArr};

// Create a global variable for the various program states for a semi-unspecified length
use std::{collections::HashMap, sync::RwLock};
static PROGRAM_STATES: RwLock, 20>> = RwLock::new(combo_vec![]);

// If we know the stack will never be larger than 20 elements,
// we can get a performance boost by using ReArr instead of ComboVec
let mut runtime_stack = ReArr::::new();
```

### Go fast with const & copy

We can take advantage of `ComboVec` and `ReArr` by creating one const context then copying it to our runtime variable.
This is much faster than creating a new `ComboVec` at runtime, and `T` does _not_ need to be `Copy`.

Here's a basic look at what this looks like:

```rust
use combo_vec::{combo_vec, ComboVec};

const SOME_ITEMS: ComboVec = combo_vec![];

for _ in 0..50 {
let mut empty_combo_vec = SOME_ITEMS;
empty_combo_vec.push("Hello".to_string());
empty_combo_vec.push("world".to_string());
println!("{}!", empty_combo_vec.join(" "));
}
```