Skip to content

Latest commit

 

History

37 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

highroller

GitHub Stars Crates.io Total Downloads GitHub Issues Current Version

A simple, high-level rolling index that is thread-safe and guarantees cheap runtime-unique IDs.

Usage

This Rust crate provides a statically available, thread-safe rolling index. Intended for simple use cases where UUIDs would be overkill and a cheap alternative is preferable.

The main function provided is rolling_idx(). Simplistically:

let id1 = highroller::rolling_idx();
let id2 = highroller::rolling_idx();
println!("Id 1 is: {}", id1);
println!("Id 2 is: {}", id2);
// outputs:
// Id 1 is: 0
// Id 2 is: 1

The function rolling_idx() returns the current index value. After retrieving, it increments the index by 1. This way, you get a unique, ever-increasing rolling index each time you call this function.

Please note that the rolling index is runtime-specific and is reset every time your application starts.

The rolling index is also thread-safe, meaning you can access it from multiple threads simultaneously without encountering issues related to concurrent data access. This is exercised by the crate's test suite, which spawns a thread per id the configured width can hand out, capped at 1000, and asserts every returned ID is unique.

Feature Flags

highroller provides several feature flags for flexibility.

Feature Flag Default Description
strict Enabled Panics on overflow. Disables comparisons and arithmetic operations between RUID and its underlying numeric type. (When disabled, overflow wraps instead.)
ruid_type Disabled Enables Rolling Unique ID (RUID) type, a wrapper over the rolling index.
allow_arithmetics Disabled Optional support for arithmetic operations on RUID (note that without strict enabled, you should know what you are doing, since it can cause ambiguous behaviour)
const Disabled Makes RUID::new() a const fn. The index is rolled on first read instead, so RUID is not Copy and has no Deref under this flag.
async Disabled Marks RUID as Send and Sync explicitly, for use across thread boundaries. With const the type is already both, so only Send is asserted.
size (separate flags) u16_index Choose the size of the rolling index: u8_index, u16_index, u32_index, u64_index, u128_index, usize_index

If you need a particular rolling index size, or if you want to implement more explicit typing with RUID, enable the features according to your use case. The strict feature will help you catch overflows, where as the allow_arithmetics flag expands RUID functionality to support arithmetic operations.

Exactly one size flag can be on at a time. Each one defines the same index type, so two of them is a duplicate definition rather than a wider index. The default is u16_index, which means picking a different size also means turning the default off:

[dependencies]
highroller = { version = "0.1", default-features = false, features = ["u32_index", "strict"] }

Turning the default off without naming a size leaves no index type at all. Both mistakes are caught at compile time with a message saying which one happened. The same constraint is why cargo build --all-features cannot work on this crate.

RUID

"Rolling Unique ID" (RUID) is a wrapper over the rolling index, with optional support for arithmetic operations, and complete equivalence relation methods and display methods. You can use the ruid_type feature flag to enable RUID and use it in your program. Read more about RUID at the "Extras" section.

Example

Consider a basic game where you summon digital fighters. Each summoned fighter needs to have a unique identifier. Creating a complex UUID for each fighter could eat up valuable resources and cause performance issues in your game.

That's where you can take advantage of highroller to assign unique identifiers. It is simple and efficient:

use std::sync::{Arc, Mutex};
use std::thread;

// the index width is a compile-time choice, so the id type follows whichever size flag is set
#[derive(Clone)]
struct Fighter<I> {
  id: I,
  power: u32,
}

// create a register for fighters
let fighters_register = Arc::new(Mutex::new(Vec::new()));

// create four threads as four different arenas
let arenas = 4;

// gather 20 fighters for each arena
let mut handlers = Vec::new();
for _ in 0..arenas {
  let fighters_register = Arc::clone(&fighters_register);
  handlers.push(thread::spawn(move || {
    let mut ids = Vec::new();
    for n in 0..20u32 {
      let id = highroller::rolling_idx();
      let fighter = Fighter {
        id,
        power: (n * 37 + 11) % 100, // stand-in for a real power stat
      };
      ids.push(fighter.id);
      fighters_register.lock().unwrap().push(fighter);
    }
    ids
  }));
}

// run a simple tournament that finds a champion for each arena
let mut champions = Vec::with_capacity(arenas);
for handler in handlers {
  let arena_fighters = handler.join().unwrap();
  let fighters = fighters_register.lock().unwrap();

  // find the fighter with the highest power in each arena
  let champion = arena_fighters.iter()
    .map(|&id| fighters.iter().find(|fighter| fighter.id == id).unwrap())
    .max_by_key(|fighter| fighter.power)
    .unwrap()
    .clone();

  champions.push(champion);
}

// match the arena champions against each other for the ultimate champion
let ultimate_champion = champions.into_iter()
  .max_by_key(|fighter| fighter.power)
  .unwrap();

// print the winner by id
println!("The ultimate champion is fighter with id: {}", ultimate_champion.id);

In this example, each fighter we create gets a unique identifier from highroller::rolling_idx(). Since the rolling index is incremental, each fighter gets a unique ID. This happens without any complex UUID or similar overhead, and is practical to use.

Please remember that the index resets every time your application restarts. If you need persistence across application restarts, you will have to implement additional strategies.

The problem

At times, you need a very simple guaranteed unique identifier for something. Using UUIDs can be overkill and bring forward resource costs you likely don't need to afford, if your use case is very simple and not very extensive.

In comes a static rolling counter.

The concept is simple: Have a statically available rolling value, that automatically increments itself after each fetch. Add in some thread-safety measures and you have a very easy-to-use and practical guaranteedly unique identifier.

Mainly useful for simple identification needs, it avoids a lot of complexities, and can be very cheap to run.

Extras

With the ruid_type feature flag, you're able to use RUIDs, or Rolling Unique Identifiers, as custom integer types. You can convert a RUID to a standard integer or vice versa, compare two RUIDs, manipulate RUIDs with arithmetic operations if allow_arithmetics flag is on, and print RUIDs as they implement the fmt::Display trait.

Example usage of RUID (requires the ruid_type feature, not enabled by default):

use highroller::RUID;

let id1 = RUID::new();
let id2 = RUID::new();

assert_ne!(id1, id2);

RUID under the const feature

RUID::new() becomes a const fn, so a RUID can be built where a constant is required:

use highroller::RUID;

static ID: RUID = RUID::new();

// no index has been rolled yet at this point. The first read takes one and keeps it, so
// every later read agrees with the first.
assert_eq!(ID.get(), ID.get());

A const fn cannot roll an index, so the value starts out unassigned and takes one on first read. That needs somewhere to write the result, which costs two things this flag otherwise leaves alone: RUID is not Copy and does not implement Deref. Use get(), and clone where a value is needed twice. The lazy assignment is thread-safe, so a RUID shared between threads resolves to one index for all of them.

Support

Whether you use this project, have learned something from it, or just like it, please consider supporting it by buying me a coffee, so I can dedicate more time on open-source projects like this :)

Buy Me A Coffee

License

You can check out the full license here

This project is licensed under the terms of the MPL-2.0 license.

About

A static runtime-unique rolling index for your simple runtime-UID needs

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages