tock_cells/
map_cell.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Tock specific `MapCell` type for sharing references.
6
7use core::cell::{Cell, UnsafeCell};
8use core::mem::MaybeUninit;
9use core::ptr::drop_in_place;
10
11#[derive(Clone, Copy, PartialEq)]
12enum MapCellState {
13    Uninit,
14    Init,
15    Borrowed,
16}
17
18#[inline(never)]
19#[cold]
20fn access_panic() {
21    panic!("`MapCell` accessed while borrowed");
22}
23
24macro_rules! debug_assert_not_borrowed {
25    ($slf:ident) => {
26        if cfg!(debug_assertions) && $slf.occupied.get() == MapCellState::Borrowed {
27            access_panic();
28        }
29    };
30}
31
32/// A mutable, possibly unset, memory location that provides checked `&mut` access
33/// to its contents via a closure.
34///
35/// A `MapCell` provides checked shared access to its mutable memory. Borrow
36/// rules are enforced by forcing clients to either move the memory out of the
37/// cell or operate on a `&mut` within a closure. You can think of a `MapCell`
38/// as a `Cell<Option<T>>` with an extra "in-use" state to prevent `map` from invoking
39/// undefined behavior when called re-entrantly.
40///
41/// # Examples
42/// ```
43/// # use tock_cells::map_cell::MapCell;
44/// let cell: MapCell<i64> = MapCell::empty();
45///
46/// assert!(cell.is_none());
47/// cell.map(|_| unreachable!("The cell is empty; map does not call the closure"));
48/// assert_eq!(cell.take(), None);
49/// cell.put(10);
50/// assert_eq!(cell.take(), Some(10));
51/// assert_eq!(cell.replace(20), None);
52/// assert_eq!(cell.get(), Some(20));
53///
54/// cell.map(|x| {
55///     assert_eq!(x, &mut 20);
56///     // `map` provides a `&mut` to the contents inside the closure
57///     *x = 30;
58/// });
59/// assert_eq!(cell.replace(60), Some(30));
60/// ```
61pub struct MapCell<T> {
62    // Since val is potentially uninitialized memory, we must be sure to check
63    // `.occupied` before calling `.val.get()` or `.val.assume_init()`. See
64    // [mem::MaybeUninit](https://doc.rust-lang.org/core/mem/union.MaybeUninit.html).
65    val: UnsafeCell<MaybeUninit<T>>,
66
67    // Safety invariants:
68    // - The contents of `val` must be initialized if this is `Init` or `InsideMap`.
69    // - It must be sound to mutate `val` behind a shared reference if this is `Uninit` or `Init`.
70    //   No outside mutation can occur while a `&mut` to the contents of `val` exist.
71    occupied: Cell<MapCellState>,
72}
73
74impl<T> Drop for MapCell<T> {
75    fn drop(&mut self) {
76        let state = self.occupied.get();
77        debug_assert_not_borrowed!(self); // This should be impossible
78        if state == MapCellState::Init {
79            unsafe {
80                // SAFETY:
81                // - `occupied` is `Init`; `val` is initialized as an invariant.
82                // - Even though this violates the `occupied` invariant, by causing `val`
83                //   to be no longer valid, `self` is immediately dropped.
84                drop_in_place(self.val.get_mut().as_mut_ptr())
85            }
86        }
87    }
88}
89
90impl<T: Copy> MapCell<T> {
91    /// Gets the contents of the cell, if any.
92    ///
93    /// Returns `None` if the cell is empty.
94    ///
95    /// This requires the held type be `Copy` for the same reason [`Cell::get`] does:
96    /// it leaves the contents of `self` intact and so it can't have drop glue.
97    ///
98    /// This returns `None` in release mode if the `MapCell`'s contents are already borrowed.
99    ///
100    /// # Examples
101    /// ```
102    /// # use tock_cells::map_cell::MapCell;
103    /// let cell: MapCell<u32> = MapCell::empty();
104    /// assert_eq!(cell.get(), None);
105    ///
106    /// cell.put(20);
107    /// assert_eq!(cell.get(), Some(20));
108    /// ```
109    ///
110    /// # Panics
111    /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed.
112    pub fn get(&self) -> Option<T> {
113        debug_assert_not_borrowed!(self);
114        // SAFETY:
115        // - `Init` means that `val` is initialized and can be read
116        // - `T: Copy` so there is no drop glue
117        (self.occupied.get() == MapCellState::Init)
118            .then(|| unsafe { self.val.get().read().assume_init() })
119    }
120}
121
122impl<T> MapCell<T> {
123    /// Creates an empty `MapCell`.
124    pub const fn empty() -> MapCell<T> {
125        MapCell {
126            val: UnsafeCell::new(MaybeUninit::uninit()),
127            occupied: Cell::new(MapCellState::Uninit),
128        }
129    }
130
131    /// Creates a new `MapCell` containing `value`.
132    pub const fn new(value: T) -> MapCell<T> {
133        MapCell {
134            val: UnsafeCell::new(MaybeUninit::new(value)),
135            occupied: Cell::new(MapCellState::Init),
136        }
137    }
138
139    /// Returns `true` if the `MapCell` contains no value.
140    ///
141    /// # Examples
142    /// ```
143    /// # use tock_cells::map_cell::MapCell;
144    /// let x: MapCell<i32> = MapCell::empty();
145    /// assert!(x.is_none());
146    ///
147    /// x.put(10);
148    /// x.map(|_| assert!(!x.is_none()));
149    /// assert!(!x.is_none());
150    /// ```
151    pub fn is_none(&self) -> bool {
152        !self.is_some()
153    }
154
155    /// Returns `true` if the `MapCell` contains a value.
156    ///
157    /// # Examples
158    /// ```
159    /// # use tock_cells::map_cell::MapCell;
160    /// let x: MapCell<i32> = MapCell::new(10);
161    /// assert!(x.is_some());
162    /// x.map(|_| assert!(x.is_some()));
163    ///
164    /// x.take();
165    /// assert!(!x.is_some());
166    /// ```
167    pub fn is_some(&self) -> bool {
168        self.occupied.get() != MapCellState::Uninit
169    }
170
171    /// Takes the value out of the `MapCell`, leaving it empty.
172    ///
173    /// Returns `None` if the cell is empty.
174    ///
175    /// To save size, this has no effect and returns `None` in release mode
176    /// if the `MapCell`'s contents are already borrowed.
177    ///
178    /// # Examples
179    ///
180    /// ```
181    /// # use tock_cells::map_cell::MapCell;
182    /// let cell = MapCell::new(1234);
183    /// let x = &cell;
184    /// let y = &cell;
185    ///
186    /// assert_eq!(x.take(), Some(1234));
187    /// assert_eq!(y.take(), None);
188    /// ```
189    ///
190    /// # Panics
191    /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed.
192    pub fn take(&self) -> Option<T> {
193        debug_assert_not_borrowed!(self);
194        (self.occupied.get() == MapCellState::Init).then(|| {
195            // SAFETY: Since `occupied` is `Init`, `val` is initialized and can be mutated
196            //         behind a shared reference. `result` is therefore initialized.
197            unsafe {
198                let result: MaybeUninit<T> = self.val.get().replace(MaybeUninit::uninit());
199                self.occupied.set(MapCellState::Uninit);
200                result.assume_init()
201            }
202        })
203    }
204
205    /// Puts a value into the `MapCell` without returning the old value.
206    ///
207    /// To save size, this has no effect in release mode if `map` is invoking
208    /// a closure for this cell.
209    ///
210    /// # Panics
211    /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed.
212    pub fn put(&self, val: T) {
213        debug_assert_not_borrowed!(self);
214        // This will ensure the value as dropped
215        self.replace(val);
216    }
217
218    /// Replaces the contents of the `MapCell`, returning the old value if available.
219    ///
220    /// To save size, this has no effect and returns `None` in release mode
221    /// if the `MapCell`'s contents are already borrowed.
222    ///
223    /// # Panics
224    /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed.
225    pub fn replace(&self, val: T) -> Option<T> {
226        let occupied = self.occupied.get();
227        debug_assert_not_borrowed!(self);
228        if occupied == MapCellState::Borrowed {
229            return None;
230        }
231        self.occupied.set(MapCellState::Init);
232
233        // SAFETY:
234        // - Since `occupied` is `Init` or `Uninit`, no `&mut` to the `val` exists, meaning it
235        //   is safe to mutate the `get` pointer.
236        // - If occupied is `Init`, `maybe_uninit_val` must be initialized.
237        let maybe_uninit_val = unsafe { self.val.get().replace(MaybeUninit::new(val)) };
238        (occupied == MapCellState::Init).then(|| unsafe { maybe_uninit_val.assume_init() })
239    }
240
241    /// Calls `closure` with a `&mut` of the contents of the `MapCell`, if available.
242    ///
243    /// The closure is only called if the `MapCell` has a value.
244    /// The state of the `MapCell` is unchanged after the closure completes.
245    ///
246    /// # Re-entrancy
247    ///
248    /// This borrows the contents of the cell while the closure is executing.
249    /// Be careful about calling methods on `&self` inside of that closure!
250    /// To save size, this has no effect in release mode, but if debug assertions
251    /// are enabled, this panics to indicate a likely bug.
252    ///
253    /// # Examples
254    ///
255    /// ```
256    /// # use tock_cells::map_cell::MapCell;
257    /// let cell = MapCell::new(1234);
258    /// let x = &cell;
259    /// let y = &cell;
260    ///
261    /// x.map(|value| {
262    ///     // We have mutable access to the value while in the closure
263    ///     *value += 1;
264    /// });
265    ///
266    /// // After the closure completes, the mutable memory is still in the cell,
267    /// // but potentially changed.
268    /// assert_eq!(y.take(), Some(1235));
269    /// ```
270    ///
271    /// # Panics
272    /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed.
273    #[inline(always)]
274    pub fn map<F, R>(&self, closure: F) -> Option<R>
275    where
276        F: FnOnce(&mut T) -> R,
277    {
278        debug_assert_not_borrowed!(self);
279        (self.occupied.get() == MapCellState::Init).then(move || {
280            self.occupied.set(MapCellState::Borrowed);
281            // `occupied` is reset to initialized at the end of scope,
282            // even if a panic occurs in `closure`.
283            struct ResetToInit<'a>(&'a Cell<MapCellState>);
284            impl Drop for ResetToInit<'_> {
285                #[inline(always)]
286                fn drop(&mut self) {
287                    self.0.set(MapCellState::Init);
288                }
289            }
290            let _reset_to_init = ResetToInit(&self.occupied);
291            unsafe { closure(&mut *self.val.get().cast::<T>()) }
292        })
293    }
294
295    /// Behaves like `map`, but returns `default` if there is no value present.
296    #[inline(always)]
297    pub fn map_or<F, R>(&self, default: R, closure: F) -> R
298    where
299        F: FnOnce(&mut T) -> R,
300    {
301        self.map(closure).unwrap_or(default)
302    }
303
304    /// Behaves the same as `map`, except the closure is allowed to return
305    /// an `Option`.
306    #[inline(always)]
307    pub fn and_then<F, R>(&self, closure: F) -> Option<R>
308    where
309        F: FnOnce(&mut T) -> Option<R>,
310    {
311        self.map(closure).flatten()
312    }
313
314    /// If a value is present `modify` is called with a borrow.
315    /// Otherwise, the value is set with `G`.
316    #[inline(always)]
317    pub fn modify_or_replace<F, G>(&self, modify: F, mkval: G)
318    where
319        F: FnOnce(&mut T),
320        G: FnOnce() -> T,
321    {
322        if self.map(modify).is_none() {
323            self.put(mkval());
324        }
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::MapCell;
331
332    struct DropCheck<'a> {
333        flag: &'a mut bool,
334    }
335    impl<'a> Drop for DropCheck<'a> {
336        fn drop(&mut self) {
337            *self.flag = true;
338        }
339    }
340
341    #[test]
342    fn test_drop() {
343        let mut dropped_after_drop = false;
344        let mut dropped_after_put = false;
345        {
346            let cell = MapCell::new(DropCheck {
347                flag: &mut dropped_after_put,
348            });
349            cell.put(DropCheck {
350                flag: &mut dropped_after_drop,
351            });
352        }
353        assert!(dropped_after_drop);
354        assert!(dropped_after_put);
355    }
356
357    #[test]
358    fn test_replace() {
359        let a_cell = MapCell::new(1);
360        let old = a_cell.replace(2);
361        assert_eq!(old, Some(1));
362        assert_eq!(a_cell.take(), Some(2));
363        assert_eq!(a_cell.take(), None);
364    }
365
366    #[test]
367    #[should_panic = "`MapCell` accessed while borrowed"]
368    fn test_map_in_borrow() {
369        let cell = MapCell::new(1);
370        let borrow1 = &cell;
371        let borrow2 = &cell;
372        borrow1.map(|_| borrow2.map(|_| ()));
373    }
374
375    #[test]
376    #[should_panic = "`MapCell` accessed while borrowed"]
377    fn test_replace_in_borrow() {
378        let my_cell = MapCell::new(55);
379        my_cell.map(|_ref1: &mut i32| {
380            // Should fail
381            my_cell.replace(56);
382        });
383    }
384
385    #[test]
386    #[should_panic = "`MapCell` accessed while borrowed"]
387    fn test_put_in_borrow() {
388        let my_cell = MapCell::new(55);
389        my_cell.map(|_ref1: &mut i32| {
390            // Should fail
391            my_cell.put(56);
392        });
393    }
394
395    #[test]
396    #[should_panic = "`MapCell` accessed while borrowed"]
397    fn test_get_in_borrow() {
398        let my_cell = MapCell::new(55);
399        my_cell.map(|_ref1: &mut i32| {
400            // Should fail
401            my_cell.get();
402        });
403    }
404}