loan_cell/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This crate provides a [`LoanCell`] type that allows for lending a reference
5//! to a value for a limited scope.
6//!
7//! This is useful for publishing a reference to an on-stack value into a
8//! thread-local variable for the lifetime of a function call. This can be
9//! useful when you can't or don't want to temporarily move the value to the
10//! thread-local variable:
11//!   - The value is not sized (e.g., it is a slice or a dyn trait object), so
12//!     you can't move it.
13//!   - The value is large, so it would be inefficient to move it.
14//!   - The value has a destructor, so putting it in TLS would use the
15//!     inefficient form of Rust TLS that registers a destructor for the value.
16//!
17//! [`LoanCell`] is not `Sync` or `Send`, so it can only be used within a single
18//! thread. This is necessary to ensure that the loaned value is not accessed
19//! after the function that loaned it returns.
20//!
21//! # Example
22//!
23//! ```rust
24//! use loan_cell::LoanCell;
25//!
26//! thread_local! {
27//!    static CONTEXT: LoanCell<str> = const { LoanCell::new() };
28//! }
29//!
30//! fn print_name() -> String {
31//!     CONTEXT.with(|name| {
32//!         name.borrow(|name| {
33//!             format!("stored {}", name.unwrap_or("nowhere"))
34//!         })
35//!     })
36//! }
37//!
38//! CONTEXT.with(|v| {
39//!     assert_eq!(v.lend(&String::from("in the heap"), || print_name()), "stored in the heap");
40//!     assert_eq!(v.lend("statically", || print_name()), "stored statically");
41//! });
42//! assert_eq!(print_name(), "stored nowhere");
43//! ```
44
45// UNSAFETY: this is needed to work around the borrow checker.
46#![expect(unsafe_code)]
47#![no_std]
48
49use core::cell::Cell;
50use core::panic::RefUnwindSafe;
51use core::panic::UnwindSafe;
52use core::ptr::NonNull;
53
54/// A cell that allows lending a reference to a value for a limited scope.
55///
56/// See the [module-level documentation](crate) for more information.
57#[derive(Default)]
58pub struct LoanCell<T: ?Sized>(Cell<Option<NonNull<T>>>);
59
60impl<T: RefUnwindSafe + ?Sized> UnwindSafe for LoanCell<T> {}
61impl<T: RefUnwindSafe + ?Sized> RefUnwindSafe for LoanCell<T> {}
62
63impl<T: ?Sized> LoanCell<T> {
64    /// Creates a `LoanCell` with no loaned data.
65    pub const fn new() -> Self {
66        Self(Cell::new(None))
67    }
68
69    /// Lends `value` for the lifetime of `f`. `f` or any function it calls can
70    /// access the loaned value via [`LoanCell::borrow`].
71    ///
72    /// If a value is already lent, it is replaced with `value` for the duration
73    /// of `f` and restored afterwards.
74    pub fn lend<R>(&self, value: &T, f: impl FnOnce() -> R) -> R {
75        // Use a guard to restore the old value after `f` returns or panics.
76        struct RestoreOnDrop<'a, T: ?Sized>(&'a LoanCell<T>, Option<NonNull<T>>);
77        impl<T: ?Sized> Drop for RestoreOnDrop<'_, T> {
78            fn drop(&mut self) {
79                self.0.0.set(self.1);
80            }
81        }
82
83        let old = self.0.replace(Some(value.into()));
84        let _guard = RestoreOnDrop(self, old);
85        f()
86    }
87
88    /// Returns `true` if a value is currently lent.
89    pub fn is_lent(&self) -> bool {
90        self.0.get().is_some()
91    }
92
93    /// Borrows the lent value for the duration of `f`. If no value is currently
94    /// lent, `f` is called with `None`.
95    pub fn borrow<R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
96        let v = self.0.get().map(|v| {
97            // SAFETY: the inner value is alive as long as the corresponding
98            // `lend` call is running, and the value is only dropped when the
99            // function passed to `lend` returns. Since `LoanCell` is not
100            // `Sync`, and so `lend` must be running on the same thread as this
101            // call, this cannot happen while `f` is running.
102            unsafe { v.as_ref() }
103        });
104        f(v)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::LoanCell;
111
112    extern crate std;
113
114    static_assertions::assert_not_impl_any!(LoanCell<()>: Sync, Send);
115
116    #[test]
117    fn loan() {
118        struct NoCopy<T>(T);
119        let cell = LoanCell::new();
120        cell.borrow(|v| assert!(v.is_none()));
121        let result = cell.lend(&NoCopy(42), || {
122            cell.borrow(|v| {
123                assert_eq!(v.unwrap().0, 42);
124            });
125            42
126        });
127        assert_eq!(result, 42);
128        cell.borrow(|v| assert!(v.is_none()));
129    }
130
131    #[test]
132    fn nested_loan() {
133        let cell = LoanCell::new();
134        cell.lend(&42, || {
135            cell.lend(&52, || {
136                cell.borrow(|v| {
137                    assert_eq!(v.unwrap(), &52);
138                });
139            });
140            cell.borrow(|v| {
141                assert_eq!(v.unwrap(), &42);
142            });
143        });
144    }
145
146    #[test]
147    fn unsized_loan() {
148        let cell = LoanCell::new();
149        let value = "hello";
150        cell.lend(value, || {
151            cell.borrow(|v| {
152                assert_eq!(v.unwrap(), value);
153            });
154        });
155    }
156
157    #[test]
158    fn panicked_loan() {
159        let cell = LoanCell::new();
160        let result = std::panic::catch_unwind(|| {
161            cell.lend(&42, || {
162                panic!();
163            });
164        });
165        assert!(result.is_err());
166        cell.borrow(|v| assert!(v.is_none()));
167    }
168}