Radium
The radium crate provides a unifying model for shared-mutability over the
primitives. It does not handle more complex shared-mutability topics such as
mutices or locks: if you need to manage large structured data, you will need to
look elsewhere.
Radium Trait
This trait allows your code to generically accept either an atomic type or a
Cell and interact with it through a unified API. It is implemented by all of
the standard library atomics, Cell<{bool,{i,u}{8,16,32,64,128,size},*mut T}>,
and the type families that Radium provides (described below).
The primitive type that a Radium implementor encloses is indicated by the
Radium::Item associated type. You can use this as a constraint in your trait
bounds (i.e. <R: Radium<Item = i32>>) in order to gain direct access to the
primitive, or you can require that R::Item implement other traits and interact
with it through them.
Type Aliases
Radium provides type aliases for each primitive which could be atomic in the
standard library. The Radium{Bool,{I,U}{8,16,32,64,128,size},Ptr} symbols all
forward to their corresponding AtomicType when that symbol exists, or to
Cell<Type> when it does not.
Since these are type aliases rather than newtypes, you can globally replace the
AtomicT symbols with their RadiumT equivalent without any other changes to
your codebase.
Type Families
Radium provides three type families which accept fundamentals as type
parameters. These families have no inherent API, and only implement Radium,
Debug, Default, and From<T>. They may or may not have a Sync
implementation, depending on whether they have atomic behavior.
-
The
Atom<T>family corresponds to (and wraps) the standard library’score::sync::atomic::*types. This family only acceptsTparameters where an equivalentAtomicTsymbol exists for the target; this means that on targets which do not have, for instance,AtomicU64,Atom<u64>will fail to compile. These are alwaysSync.Atomrequires that type arguments implementradium::marker::Atomic. -
The
Isotope<T>family functions similarly toAtom<T>, except that it wraps Radium’sRadiumTtype aliases. As such,Isotopeis portable across targets with different atomic supports, and will never fail to compile; however, it will silently degrade from atomic toCellbehavior (including loss ofSync) when the requisite atomic types are missing.Isotoperequires that type arguments implementradium::marker::Nuclear. -
The
Radon<T>family is a wrapper overCell<T>. LikeIsotope, it requires that type arguments implementradium::marker::Nuclear. It is neverSync.
Examples
This contrived example is taken from radium/examples/schroedinger.rs. It
shows how the Radium trait can be used by a worker function to manipulate
data, and how the different types can be used to work in sequence or in
parallel.
Note: Radium’s MSRV is 1.60, while the scoped-threads API used here stabilized in 1.63.
use radium::{Radium, types::{RadiumU64, Atom, Isotope, Radon}};
use std::{
cell::Cell,
sync::atomic::{AtomicU64, Ordering},
thread,
time::Duration,
};
fn do_work<R: Radium<Item = u64>>(this: &R, ident: u8) {
let on_entry = this.load(Ordering::SeqCst);
println!("{: >2} step 0 sees: {: >2}", ident, on_entry);
let before_add = this.fetch_add(10, Ordering::SeqCst);
println!("{: >2} step 1 sees: {: >2}", ident, before_add);
let after_add = this.load(Ordering::SeqCst);
println!("{: >2} step 2 sees: {: >2}", ident, after_add);
thread::sleep(Duration::from_millis(after_add));
let before_sub = this.fetch_sub(3, Ordering::SeqCst);
println!("{: >2} step 3 sees: {: >2}", ident, before_sub);
let on_exit = this.load(Ordering::SeqCst);
println!("{: >2} step 4 sees: {: >2}", ident, on_exit);
}
static ATOM: AtomicU64 = AtomicU64::new(0);
static RADIUM: RadiumU64 = RadiumU64::new(0);
fn main() {
let cell = Cell::new(0u64);
let atom = Atom::new(0u64);
let isotope = Isotope::new(0u64);
let radon = Radon::new(0u64);
println!("atoms");
thread::scope(|s| for ident in 0 .. 3 {
s.spawn(move || do_work(&ATOM, ident));
});
println!();
thread::scope(|s| for ident in 3 .. 6 {
let atom = &atom;
s.spawn(move || do_work(atom, ident));
});
println!();
println!("isotopes");
thread::scope(|s| for ident in 6 .. 9 {
s.spawn(move || do_work(&RADIUM, ident));
});
println!();
for ident in 9 .. 12 {
do_work(&isotope, ident);
}
println!();
println!("cells");
for ident in 12 .. 15 {
do_work(&cell, ident);
}
println!();
for ident in 15 .. 18 {
do_work(&radon, ident);
}
println!();
}