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}