pure_cell/lib.rs
1// Pure Cell
2// Copyright © 2022 Jeron Aldaron Lau.
3//
4// Licensed under any of:
5// - Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
6// - MIT License (https://mit-license.org/)
7// - Boost Software License, Version 1.0 (https://www.boost.org/LICENSE_1_0.txt)
8// At your choosing (See accompanying files LICENSE_APACHE_2_0.txt,
9// LICENSE_MIT.txt and LICENSE_BOOST_1_0.txt).
10//
11//! Alternative to `GhostCell` that provides safe interior mutability via const
12//! expressions.
13//!
14//! ## Advantages
15//! - Simple, with no cell keys
16//! - Works better with thread local global state.
17//!
18//! ## Disadvantages
19//! - Const closures/fn pointers don't exist (yet), so this crate depends on
20//! macro magic to sort-of-polyfill them
21//! - Might not always optimize well (TODO)
22//!
23//! Once const contexts support mutable references, this crate will be able to
24//! remove the second disadvantage. Additionally, once const function pointers
25//! stabilize, this crate will be able to remove the first disadvantage.
26//!
27//! # Getting Started
28//! ```rust
29//! use pure_cell::{PureCell, pure_cell};
30//!
31//! let mut cell = PureCell::new(15);
32//! pure_cell!(cell, (), |state: u32, _args: ()| {
33//! state += 1;
34//! });
35//! let got = cell.get();
36//! assert_eq!(*got, 16);
37//! ```
38//!
39//! ```rust
40//! use pure_cell::{PureCell, pure_cell};
41//!
42//! let cell = PureCell::new(15);
43//! let amount = 2;
44//! let state = pure_cell!(cell, amount, |state: u32, amount: u32| -> u32 {
45//! state += amount;
46//! state
47//! });
48//! assert_eq!(state, 17);
49//! ```
50
51#![no_std]
52#![doc(
53 html_logo_url = "https://ardaku.github.io/mm/logo.svg",
54 html_favicon_url = "https://ardaku.github.io/mm/icon.svg",
55 html_root_url = "https://docs.rs/pure_cell"
56)]
57#![warn(
58 anonymous_parameters,
59 missing_copy_implementations,
60 missing_debug_implementations,
61 missing_docs,
62 nonstandard_style,
63 rust_2018_idioms,
64 single_use_lifetimes,
65 trivial_casts,
66 trivial_numeric_casts,
67 unreachable_pub,
68 unused_extern_crates,
69 unused_qualifications,
70 variant_size_differences
71)]
72
73use core::{cell::UnsafeCell, mem::ManuallyDrop};
74
75/// A cell type that provides interior mutability via "pure" functions.
76#[derive(Debug)]
77pub struct PureCell<T> {
78 value: UnsafeCell<ManuallyDrop<T>>,
79}
80
81impl<T> PureCell<T> {
82 /// Creates a new `PureCell` containing the given value.
83 pub const fn new(value: T) -> Self {
84 Self {
85 value: UnsafeCell::new(ManuallyDrop::new(value)),
86 }
87 }
88
89 /// Returns a mutable reference to the underlying data.
90 pub fn get(&mut self) -> &mut T {
91 self.value.get_mut()
92 }
93
94 /// Update cell.
95 ///
96 /// # Safety
97 /// Sound to use so long as you follow these rules in the closure:
98 ///
99 /// - Must not yield to other code (usually async)
100 /// - Must not recursively call `Self::with()`
101 pub unsafe fn with<R, F>(&self, f: F) -> R
102 where
103 F: FnOnce(&mut ManuallyDrop<T>) -> R,
104 {
105 f(&mut *self.value.get())
106 }
107}
108
109impl<T> Drop for PureCell<T> {
110 fn drop(&mut self) {
111 unsafe {
112 let _ = ManuallyDrop::take(&mut *self.value.get());
113 }
114 }
115}
116
117/// Main safe mechanism to mutate [`PureCell`] via a `const` expression.
118#[macro_export]
119macro_rules! pure_cell {
120 (
121 $pure_cell:expr,
122 $input:expr,
123 |$state:ident: $ty:ty, $args:ident: $argty:ty| -> $ret:ty $block:block
124 ) => ({
125 #[inline(always)]
126 const fn const_fn(mut $state: $ty, mut $args: $argty) -> ($ty, $ret) {
127 let (output, state) = ($block, $state);
128 (state, output)
129 }
130 fn wrapper_fn(
131 state: &mut core::mem::ManuallyDrop<$ty>,
132 input: $argty,
133 ) -> $ret {
134 unsafe {
135 let (new, out) = const_fn(
136 core::mem::ManuallyDrop::take(state),
137 input,
138 );
139 *state = core::mem::ManuallyDrop::new(new);
140 out
141 }
142 }
143 unsafe {
144 $pure_cell.with(move |state| wrapper_fn(state, $input))
145 }
146 });
147 ($pure_cell:expr, $input:expr, |$state:ident: $ty:ty, $args:ident: $argty:ty| $block:block) => (
148 $crate::pure_cell!($pure_cell, $input, |$state: $ty, $args: $argty| -> () $block)
149 );
150}