Thread-safe cell based on atomic pointers to externally stored data

Overview

Simple thread-safe cell

PtrCell is an atomic cell type that allows safe, concurrent access to shared data. No std, no data races, no nasal demons (UB), and most importantly, no locks

This type is only useful in scenarios where you need to update a shared value by moving in and out of it. If you want to concurrently update a value through mutable references and don't require support for environments without the standard library (no_std), take a look at the standard Mutex and RwLock instead

Offers:

  • Ease of use: The API is fairly straightforward
  • Performance: The algorithms are at most a couple of instructions long

Limits:

  • Access: To see what's stored inside a cell, you must either take the value out of it or provide exclusive access (&mut) to the cell

Table of Contents

Installation

To add ptr_cell to your crate's dependencies, run the following command in your project directory:

cargo add ptr_cell

This will add ptr_cell to your Cargo.toml file, allowing you to use the library in your crate. Alternatively, you can do this by manually adding the following lines to the file:

[dependencies.ptr_cell]
version = "2.1.1"

Usage

use ptr_cell::Semantics;

// Construct a cell
let cell: ptr_cell::PtrCell<u16> = 0x81D.into();

// Replace the value inside the cell
assert_eq!(cell.replace(Some(2047), Semantics::Relaxed), Some(0x81D));

// Check whether the cell is empty
assert_eq!(cell.is_empty(Semantics::Relaxed), false);

// Take the value out of the cell
assert_eq!(cell.take(Semantics::Relaxed), Some(2047))

Semantics

PtrCell allows you to specify memory ordering semantics for its internal atomic operations through the Semantics enum. Choosing appropriate semantics is crucial for achieving the desired level of synchronization and performance. The available semantics are:

  • Ordered: Noticeable overhead, strict
  • Coupled: Acceptable overhead, intuitive
  • Relaxed: Little overhead, unconstrained

Coupled is what you'd typically use. However, other orderings have their use cases too. For example, the Relaxed semantics could be useful when the operations are already ordered through other means, like fences. As always, the documentation for each item contains more details

Examples

Find the maximum value of a sequence of numbers by concurrently processing both of the sequence's halves

fn main() {
    // Initialize an array of random numbers
    const VALUES: [u8; 11] = [47, 12, 88, 45, 67, 34, 78, 90, 11, 77, 33];

    // Construct a cell to hold the current maximum value
    let cell = ptr_cell::PtrCell::default();
    let maximum = std::sync::Arc::new(cell);

    // Slice the array in two
    let (left, right) = VALUES.split_at(VALUES.len() / 2);

    // Start a worker thread for each half
    let handles = [left, right].map(|half| {
        // Clone `maximum` to move it into the worker
        let maximum = std::sync::Arc::clone(&maximum);

        // Spawn a thread to run the maximizer
        std::thread::spawn(move || maximize_in(half, &maximum))
    });

    // Wait for the workers to finish
    for worker in handles {
        // Check whether a panic occured
        if let Err(payload) = worker.join() {
            // Thread panicked, propagate the panic
            std::panic::resume_unwind(payload)
        }
    }

    // Check the found maximum
    assert_eq!(maximum.take(), Some(90))
}

/// Inserts the maximum of `sequence` and `buffer` into `buffer`
///
/// At least one swap takes place for each value of `sequence`
fn maximize_in<T>(sequence: &[T], buffer: &ptr_cell::PtrCell<T>)
where
    T: Ord + Copy,
{
    // Iterate over the slice
    for &item in sequence {
        // Wrap the item to make the cell accept it
        let mut slot = Some(item);

        // Try to insert the value into the cell
        loop {
            // Replace the cell's value
            let previous = buffer.replace(slot, ptr_cell::Semantics::Relaxed);

            // Determine whether the swap resulted in a decrease of the buffer's value
            match slot < previous {
                // It did, insert the old value back
                true => slot = previous,
                // It didn't, move on to the next item
                false => break,
            }
        }
    }
}

Contributing

Yes, please! See CONTRIBUTING.md

License

Either CC0 1.0 Universal or the Apache License 2.0. See LICENSE.md for more details

You might also like...
Next-generation, type-safe CLI parser for Rust

Next-generation, type-safe CLI parser for Rust

Safe OCaml-Rust Foreign Function Interface

ocaml-rust This repo contains code for a proof of concept for a safe OCaml-Rust interop inspired by cxx. This is mostly optimized for calling Rust cod

An abstract, safe, and concise color conversion library for rust nightly This requires the feature adt_const_params

colortypes A type safe color conversion library This crate provides many methods for converting between color types. Everything is implemented abstrac

Safe Unix shell-like parameter expansion/variable substitution via cross-platform CLI or Rust API
Safe Unix shell-like parameter expansion/variable substitution via cross-platform CLI or Rust API

Safe Unix shell-like parameter expansion/variable substitution for those who need a more powerful alternative to envsubst but don't want to resort to

A safe, fast, lightweight embeddable scripting language written in Rust.

Bud (budlang) A safe, fast, lightweight embeddable scripting language written in Rust. WARNING: This crate is not anywhere near being ready to publish

🗄️ A simple (and safe!) to consume history of Client and Studio deployment versions.

🗄️ Roblox Version Archive A simple (and safe!) to consume history of Client and Studio deployment versions. About Parsing Roblox's DeployHistory form

A comprehensive collection of resources and learning materials for Rust programming, empowering developers to explore and master the modern, safe, and blazingly fast language.

🦀 Awesome Rust Lang ⛰️ Project Description : Welcome to the Awesome Rust Lang repository! This is a comprehensive collection of resources for Rust, a

nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor.

🔗 nvim-oxi nvim-oxi provides safe and idiomatic Rust bindings to the rich API exposed by the Neovim text editor. The project is mostly intended for p

The safe, fast and sane package manager for Linux

moss-rs A rewrite of the Serpent OS tooling in Rust, enabling a robust implementation befitting Serpent and Solus We will initially focus on moss and

Comments
  • Use the correct variant in the top-level example

    Use the correct variant in the top-level example

    The example currently uses Semantics::Coupled, which is frequently selected as the default. However, this specific algorithm is order-agnostic (able to correctly operate with the relaxed ordering). So, the semantics are needlessly restrictive. This updates the variant to Semantics::Relaxed

    opened by KDFJW 1
  • Redesign the cell's usage of Semantics

    Redesign the cell's usage of Semantics

    Requiring the cell to have default Semantics was an obvious mistake in the API. It was done this way because I've overlooked the fact that Drop::drop requires exclusive access (&mut self)

    This branch fixes this by making the accessors take a variant of Semantics. It also makes the documentation a bit nicer and, in the case of Semantics, more beginner-friendly

    opened by KDFJW 0
  • Support no_std environments

    Support no_std environments

    Currently, the library already uses std's re-exports of types from core and alloc. There's obviously no point in including the entire std. This branch updates the library to use core and alloc directly, explicitly removing std from dependencies

    opened by KDFJW 0
  • Support for linked lists

    Support for linked lists

    Because the raw pointer methods are not yet exposed, building atomic linked lists using the cell is practically impossible. The new branch fixes this by adding a safe interface for such operations

    opened by KDFJW 0
Releases(v2.1.0)
  • v2.1.0(Apr 10, 2024)

    Added

    • PtrCell::heap_leak: Associated function for giving up ownership of data
    • PtrCell::heap_reclaim: Inverse of PtrCell::heap_leak
    • PtrCell::{from_ptr, replace_ptr, take_ptr}: Pointer-based alternatives to some existing methods
    • PtrCell::get_ptr: Getter for the pointer of PtrCell
    • A section on Semantics in PtrCell's methods to the cell's documentation
    • Comments to the usage of Semantics::{read, read_write, write}

    Changed

    • Used cleaner examples for PtrCell::{is_empty, replace}

    Fixed

    • Broken links in the documentation
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Apr 5, 2024)

    Added

    • Links to helpful resources in the documentation for Semantics

    Changed

    • PtrCell now has the same in-memory representation as a *mut T
    • PtrCell::new doesn't require a Semantics variant anymore
    • PtrCell::{is_empty, replace, take, map_owner} now require a Semantics variant
    • The documentation for Semantics::Coupled now better reflects the reality
    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Mar 23, 2024)

  • v1.1.0(Mar 23, 2024)

  • v1.0.0(Mar 20, 2024)

Owner
Nikolay Levkovsky
Nikolay Levkovsky
An implementation of Joshua Yanovski's Ghost Cell paper.

A novel safe and zero-cost borrow-checking paradigm from the GhostCell paper. Motivation A number of collections, such as linked-lists, binary-trees,

null 350 Dec 22, 2022
Like a cell, but make lifetimes dynamic instead of ownership

LendingCell is a mutable container that allows you to get an owned reference to the same object. When the owned reference is dropped, ownership return

Kalle Samuels 19 Dec 15, 2022
Statically allocated, runtime initialized cell.

static-cell Statically allocated, initialized at runtime cell. StaticCell provides a no-std-compatible, no-alloc way to reserve memory at compile time

null 4 Oct 2, 2022
An alternative to `qcell` and `ghost-cell` that instead uses const generics

Purpose This crate is another attempt at the ghost-cell / qcell saga of cell crates. This provides an alternative to std::cell::RefCell that can allow

SpencerBeige 5 Feb 9, 2023
Mirroring remote repositories to s3 storage, with atomic updates and periodic garbage collection.

rsync-sjtug WIP: This project is still under development, and is not ready for production use. rsync-sjtug is an open-source project designed to provi

SJTUG 57 Feb 22, 2023
Another approach to thread stack spoofing.

Description This Twitter thread inspired the creation of this tool. Unwinder is a PoC of how to parse PE's UNWIND_INFO structs in order to achieve "pr

Kurosh Dabbagh Escalante 132 Jan 6, 2023
Concurrent and multi-stage data ingestion and data processing with Rust+Tokio

TokioSky Build concurrent and multi-stage data ingestion and data processing pipelines with Rust+Tokio. TokioSky allows developers to consume data eff

DanyalMh 29 Dec 11, 2022
A diff-based data management language to implement unlimited undo, auto-save for games, and cloud-apps which needs to retain every change.

Docchi is a diff-based data management language to implement unlimited undo, auto-save for games, and cloud-apps which needs to save very often. User'

juzy 21 Sep 19, 2022
RedMaple offers an oppinionated yet extremely flexible data modeling system based on events for back-end applications.

RedMaple offers an oppinionated yet extremely flexible data modeling system based on events for back-end applications.

Amir Alesheikh 4 Mar 5, 2023
a Rust library implementing safe, lightweight context switches, without relying on kernel services

libfringe libfringe is a library implementing safe, lightweight context switches, without relying on kernel services. It can be used in hosted environ

edef 473 Dec 28, 2022