Simple shared types for multi-threaded Rust programs: keepcalm
gives you permission to simplify your synchronization code in concurrent Rust applications.
Name inspired by @luser's Keep Calm and Call Clone.
This library simplifies a number of shared-object patterns that are used in multi-threaded programs such as web-servers.
Advantages of keepcalm
:
Shared
] or [SharedMut
], no matter whether it's
a mutex, read/write lock, read/copy/update primitive, or a read-only shared [std::sync::Arc
].project!
]able, which means you can adjust the granularity of your locks at any time without having to refactor the whole
system. If you want finer-grained locks at a later date, the code that uses the shared containers doesn't change!Send
thanks to the parking_lot
crate.panic!
s while the lock is being held). If you don't want to
poison on panic!
, constructors are available to disable this option entirely.static
Global locks for both Sync
and !Sync
objects are easily constructed using [SharedGlobal
], and can provide [Shared
]
containers. NOTE: This requires the --feature global_experimental
flagThe following container types are available:
| Container | Equivalent | Notes |
|---------------------------|-----------------------|-------|
| [SharedMut::new
] | Arc<RwLock<T>>
| This is the default shared-mutable type.
| [SharedMut::new_mutex
] | Arc<Mutex<T>>
| In some cases it may be necessary to serialize both read and writes. For example, with types that are not Sync
.
| [SharedMut::new_rcu
] | Arc<RwLock<Arc<T>
| When the write lock of an RCU container is dropped, the values written are committed to the value in the container.
| [Shared::new
] | Arc
| This is the default shared-immutable type. Note that this is slightly more verbose: [Shared
] does not [std::ops::Deref
] to the underlying type and requires calling [Shared::read
].
| [Shared::new_mutex
] | Arc<Mutex<T>>
| For types that are not Sync
, a Mutex
is used to serialize read-only access.
| [SharedMut::shared
] | n/a | This provides a read-only view into a read-write container and has no direct equivalent.
The traditional Rust shared object patterns tend to be somewhat verbose and repetitive, for example:
```rust
struct Foo {
mystring: Arc
If we want to switch our shared fields from [std::sync::Mutex
] to [std::sync::RwLock
], we need to change four lines just for types, and
switch the lock
method for a read
method.
We can increase flexibility, and reduce some of the ceremony and verbosity with keepcalm
:
```rust
struct Foo {
mystring: SharedMut
If we want to use a Mutex
instead of the default RwLock
that [SharedMut
] uses under the hood, we only need to change [SharedMut::new
] to
[SharedMut::new_mutex
]!
The [SharedMut
] object hides the complexity of managing Arc<Mutex<T>>
, Arc<RwLock<T>>
, and other synchronization types
behind a single interface:
```rust
let object = "123".to_string(); let shared = SharedMut::new(object); shared.read(); ```
By default, a [SharedMut
] object uses Arc<RwLock<T>>
under the hood, but you can choose the synchronization primitive at
construction time. The [SharedMut
] object erases the underlying primitive and you can use them interchangeably:
```rust
fn use_shared(shared: SharedMut
let shared = SharedMut::new("123".tostring()); useshared(shared); let shared = SharedMut::newmutex("123".tostring()); use_shared(shared); ```
Managing the poison state of synchronization primitives can be challenging as well. Rust will poison a Mutex
or RwLock
if you
hold a lock while a panic!
occurs.
The SharedMut
type allows you to specify a [PoisonPolicy
] at construction time. By default, if a synchronization
primitive is poisoned, the SharedMut
will panic!
on access. This can be configured so that poisoning is ignored:
```rust
let shared = SharedMut::newwithpolicy("123".to_string(), PoisonPolicy::Ignore); ```
The default [Shared
] object is similar to Rust's [std::sync::Arc
], but adds the ability to project. [Shared
] objects may also be
constructed as a Mutex
, or may be a read-only view into a [SharedMut
].
Note that because of this flexibility, the [Shared
] object is slightly more complex than a traditional [std::sync::Arc
], as all accesses
must be performed through the [Shared::read
] accessor.
NOTE: This requires the --feature global_experimental
flag
Global [Shared
] references can be created using [SharedGlobal
].
```rust
static GLOBAL: SharedGlobal
fn useglobal() {
let shared: Shared
Both [Shared
] and [SharedMut
] allow projection into the underlying type. Projection can be used to select
either a subset of a type, or to cast a type to a trait. The [project!
] and [project_cast!
] macros can simplify
this code.
Note that projections are always linked to the root object! If a projection is locked, the root object is locked.
Casting:
```rust
let shared = SharedMut::new("123".tostring());
let sharedasref: SharedMut
Subset of a struct/tuple:
```rust
struct Foo { tuple: (String, usize) }
let shared = SharedMut::new(Foo::default());
let shared_string: SharedMut
shared_string.write() += "hello, world"; assert_eq!(shared.read().tuple.0, "hello, world"); assert_eq!(shared_string.read(), "hello, world"); ```
Both [Shared
] and [SharedMut
] support unsized types, but due to current limitations in the language (see [std::ops::CoerceUnsized
] for details),
you need to construct them in special ways.
Unsized traits are supported, but you will either need to specify Send + Sync
in the shared type, or [project_cast!
] the object:
```rust
// In this form, Send + Sync
are visible in the shared type
let boxed: Box
// In this form, Send + Sync
are erased via projection
let shared = SharedMut::new("123".tostring());
let sharedasref: SharedMut
Unsized slices are supported using a box:
```rust
let boxed: Box<[i32]> = Box::new([1, 2, 3]); let shared: SharedMut<[i32]> = SharedMut::from_box(boxed); ```