#borrow #thread #alive #default #data #operation #scope #approach #boundaries #atomic-lend-cell

atomic-lend-cell

A Rust library for lending immutable references across threads with explicit owner-borrower semantics, offering both atomic reference counting and lightweight flag-based implementations

1 unstable release

Uses new Rust 2024

new 0.1.0 Apr 26, 2025

#249 in Concurrency

MIT license

22KB
216 lines

AtomicLendCell

A thread-safe Rust container that allows lending references to data across thread boundaries with clear ownership semantics.

Why AtomicLendCell?

Rust's ownership model excels at preventing memory safety issues, but sometimes the borrow checker is too restrictive for certain patterns, especially across thread boundaries. When the compiler can't statically verify that borrowing relationships are safe, developers often turn to Arc<T> as a solution, but this comes with downsides:

  1. Shared ownership overhead: Arc<T> creates shared ownership where any handle can keep data alive
  2. Reference counting cost: Each clone and drop requires atomic operations
  3. Unclear ownership intent: There's no distinction between "owner" and "borrower"

AtomicLendCell is designed for scenarios where:

  • You have a clear primary owner of some data
  • You know the owner will outlive all borrows
  • You need to share references across threads or non-lexical scopes
  • You want to express a lending relationship rather than shared ownership

Features

  • Thread-safe borrowing: Share immutable references across thread boundaries
  • Explicit ownership model: Clear distinction between the owner and borrowers
  • Sync-only requirement: Only requires T: Sync, not T: Send + Sync
  • Implementation options: Choose between reference counting or flag-based approaches

Usage

Add the dependency to your Cargo.toml:

[dependencies]
atomic-lend-cell = "0.1.0"

Basic Example

use atomic_lend_cell::AtomicLendCell;
use std::thread;

fn main() {
    // Create a cell with some data
    let cell = AtomicLendCell::new(vec![1, 2, 3, 4, 5]);
    
    // Borrow the data
    let borrow = cell.borrow();
    
    // Use the borrow in another thread
    let handle = thread::spawn(move || {
        // Access the data through the borrow
        println!("Data in thread: {:?}", *borrow);
    });
    
    // Meanwhile, the original thread can still access the data
    println!("Original data: {:?}", *cell);
    
    // Wait for the thread to complete
    handle.join().unwrap();
}

Implementation Options

This library offers two different implementations with different performance characteristics:

Reference Counting (similar to Arc)

[dependencies]
atomic-lend-cell = { version = "0.1.0", default-features = false, features = ["ref-counting"] }

This implementation:

  • Tracks exact reference counts
  • Provides stronger safety guarantees
  • Has higher performance overhead due to atomic operations on each borrow/drop

Flag-based (default)

[dependencies]
atomic-lend-cell = "0.1.0"  # Uses flag-based by default

This implementation:

  • Uses a single atomic flag instead of reference counting
  • Has less overhead for borrowing operations
  • Relies more heavily on correct usage patterns

Safety

AtomicLendCell enforces safety by ensuring:

  1. The owner cannot be dropped while borrows exist (debug builds)
  2. Multiple threads can safely access the same data concurrently
  3. Thread-safety is guaranteed through appropriate atomic operations

In debug builds, violations of the borrowing contract will cause panics to catch issues early. In release builds, some checks may be optimized away for performance.

Safety Considerations

⚠️ Important Safety Warning

Both implementations will panic if the AtomicLendCell is dropped while active borrowers exist, however:

  • Reference counting implementation: Will reliably panic as soon as the owner is dropped with active borrows, providing strong safety guarantees.

  • Flag-based implementation: The panic is based on checking an atomic flag during specific operations. In rare cases with concurrent access across threads, a segmentation fault might occur before the panic is triggered, particularly in release builds or high-concurrency scenarios.

If your application requires absolute memory safety guarantees, consider:

  1. Using the reference counting implementation (ref-counting feature)
  2. Adding additional synchronization to ensure all borrows complete before dropping the owner
  3. Using standard library alternatives like Arc<T> when appropriate

The flag-based implementation (default) prioritizes performance at the cost of some safety guarantees, so use it when you're confident about your borrowing patterns and ownership lifecycle.

When to Use

AtomicLendCell is ideal for:

  • Service objects that need to lend data to worker threads
  • Long-lived objects created at program start that need to be accessed from multiple threads
  • Data pipelines where ownership is clear but standard borrowing is too restrictive
  • Scenarios where performance matters and Arc introduces too much overhead

When Not to Use

Consider alternatives when:

  • You need mutable access (consider RwLock or Mutex)
  • Ownership is truly shared (use Arc)
  • The borrowing relationship isn't clearly defined
  • The owner might be dropped before borrows

License

This project is licensed under the MIT License - see the LICENSE file for details.

No runtime deps

Features