moveit/slot.rs
1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Explicit stack slots, which can be used for stack emplacement.
16//!
17//! A [`Slot`] is uninitialized storage on the stack that can be manipulated
18//! explicitly. Notionally, a [`Slot<T>`] represents a `let x: T;` in some
19//! function's stack.
20//!
21//! [`Slot`]s mut be created with the [`slot!()`] macro:
22//! ```
23//! # use moveit::{slot};
24//! slot!(storage);
25//! let mut x = storage.put(42);
26//! *x /= 2;
27//! assert_eq!(*x, 21);
28//! ```
29//! Unfortunately, due to the constrains of Rust today, it is not possible to
30//! produce a [`Slot`] as part of a larger expression; since it needs to expand
31//! to a `let` to bind the stack location, [`slot!()`] must be a statement, not
32//! an expression.
33//!
34//! [`Slot`]s can also be used to implement a sort of "guaranteed RVO":
35//! ```
36//! # use moveit::{slot, Slot, move_ref::MoveRef};
37//! fn returns_on_the_stack(val: i32, storage: Slot<i32>) -> Option<MoveRef<i32>> {
38//! if val == 0 {
39//! return None
40//! }
41//! Some(storage.put(val))
42//! }
43//!
44//! slot!(storage);
45//! let val = returns_on_the_stack(42, storage);
46//! assert_eq!(*val.unwrap(), 42);
47//! ```
48//!
49//! [`Slot`]s provide a natural location for emplacing values on the stack.
50//! The [`moveit!()`] macro is intended to make this operation
51//! straight-forward.
52//!
53//! # [`DroppingSlot`]
54//!
55//! [`DroppingSlot`] is a support type similar to [`Slot`] that is used for
56//! implementing [`DerefMove`], but which users should otherwise not construct
57//! themselves (despite it being otherwise perfectly safe to do so).
58
59use core::mem;
60use core::mem::MaybeUninit;
61use core::pin::Pin;
62use core::ptr;
63
64use crate::drop_flag::DropFlag;
65use crate::move_ref::MoveRef;
66use crate::new;
67use crate::new::New;
68use crate::new::TryNew;
69
70#[cfg(doc)]
71use {
72 crate::{move_ref::DerefMove, moveit, slot},
73 alloc::boxed::Box,
74};
75
76/// An empty slot on the stack into which a value could be emplaced.
77///
78/// The `'frame` lifetime refers to the lifetime of the stack frame this
79/// `Slot`'s storage is allocated on.
80///
81/// See [`slot!()`] and [the module documentation][self].
82pub struct Slot<'frame, T> {
83 ptr: &'frame mut MaybeUninit<T>,
84 drop_flag: DropFlag<'frame>,
85}
86
87impl<'frame, T> Slot<'frame, T> {
88 /// Creates a new `Slot` with the given pointer as its basis.
89 ///
90 /// To safely construct a `Slot`, use [`slot!()`].
91 ///
92 /// # Safety
93 ///
94 /// `ptr` must not be outlived by any other pointers to its allocation.
95 ///
96 /// `drop_flag`'s value must be dead, and must be a drop flag governing
97 /// the destruction of `ptr`'s storage in an appropriate manner as described
98 /// in [`moveit::drop_flag`][crate::drop_flag].
99 pub unsafe fn new_unchecked(
100 ptr: &'frame mut MaybeUninit<T>,
101 drop_flag: DropFlag<'frame>,
102 ) -> Self {
103 Self { ptr, drop_flag }
104 }
105
106 /// Put `val` into this slot, returning a new [`MoveRef`].
107 pub fn put(self, val: T) -> MoveRef<'frame, T> {
108 unsafe {
109 // SAFETY: Pinning is conserved by this operation.
110 Pin::into_inner_unchecked(self.pin(val))
111 }
112 }
113
114 /// Pin `val` into this slot, returning a new, pinned [`MoveRef`].
115 pub fn pin(self, val: T) -> Pin<MoveRef<'frame, T>> {
116 self.emplace(new::of(val))
117 }
118
119 /// Emplace `new` into this slot, returning a new, pinned [`MoveRef`].
120 pub fn emplace<N: New<Output = T>>(self, new: N) -> Pin<MoveRef<'frame, T>> {
121 match self.try_emplace(new) {
122 Ok(x) => x,
123 Err(e) => match e {},
124 }
125 }
126
127 /// Try to emplace `new` into this slot, returning a new, pinned [`MoveRef`].
128 pub fn try_emplace<N: TryNew<Output = T>>(
129 self,
130 new: N,
131 ) -> Result<Pin<MoveRef<'frame, T>>, N::Error> {
132 unsafe {
133 self.drop_flag.inc();
134 new.try_new(Pin::new_unchecked(self.ptr))?;
135 Ok(MoveRef::into_pin(MoveRef::new_unchecked(
136 self.ptr.assume_init_mut(),
137 self.drop_flag,
138 )))
139 }
140 }
141
142 /// Converts this into a slot for a pinned `T`.
143 ///
144 /// This is safe, since this `Slot` owns the referenced data, and
145 /// `Pin` is explicitly a `repr(transparent)` type.
146 pub fn into_pinned(self) -> Slot<'frame, Pin<T>> {
147 unsafe { self.cast() }
148 }
149
150 /// Converts this `Slot` from being a slot for a `T` to being a slot for
151 /// some other type `U`.
152 ///
153 /// ```
154 /// # use moveit::{Slot, MoveRef};
155 /// moveit::slot!(place: u32);
156 /// let foo: MoveRef<u16> = unsafe { place.cast::<u16>() }.put(42);
157 /// ```
158 ///
159 /// # Safety
160 ///
161 /// `T` must have at least the size and alignment as `U`.
162 pub unsafe fn cast<U>(self) -> Slot<'frame, U> {
163 debug_assert!(mem::size_of::<T>() >= mem::size_of::<U>());
164 debug_assert!(mem::align_of::<T>() >= mem::align_of::<U>());
165 Slot {
166 ptr: &mut *self.ptr.as_mut_ptr().cast(),
167 drop_flag: self.drop_flag,
168 }
169 }
170}
171
172impl<'frame, T> Slot<'frame, Pin<T>> {
173 /// Converts this into a slot for an unpinned `T`.
174 ///
175 /// This is safe, since this `Slot` owns the referenced data, and
176 /// `Pin` is explicitly a `repr(transparent)` type.
177 ///
178 /// Moreover, no actual unpinning is occurring: the referenced data must
179 /// be uninitialized, so it cannot have a pinned referent.
180 pub fn into_unpinned(self) -> Slot<'frame, T> {
181 unsafe { self.cast() }
182 }
183}
184
185/// Similar to a [`Slot`], but able to drop its contents.
186///
187/// A `DroppingSlot` wraps a [`Slot`], and will drop its contents if the
188/// [`Slot`]'s drop flag is dead at the time of the `DroppingSlot`'s
189/// destruction.
190///
191/// This type has an API similar to [`Slot`]'s, but rather than returning
192/// `MoveRef`s, which would own the contents of this slot, we return a `&mut T`
193/// and a [`DropFlag`], which the caller can assemble into an
194/// appropriately-shaped `MoveRef`. The drop flag will be one decrement away
195/// from being dead; callers should make sure to decremement it to trigger
196/// destruction.
197///
198/// `DroppingSlot` is intended to be used with [`DerefMove::deref_move()`],
199/// and will usually not be created by `moveit`'s users. However, [`slot!()`]
200/// provides `DroppingSlot` support, too. These slots will silently forget their
201/// contents if the drop flag is left untouched, rather than crash.
202pub struct DroppingSlot<'frame, T> {
203 ptr: &'frame mut MaybeUninit<T>,
204 drop_flag: DropFlag<'frame>,
205}
206
207impl<'frame, T> DroppingSlot<'frame, T> {
208 /// Creates a new `DroppingSlot` with the given pointer as its basis.
209 ///
210 /// To safely construct a `DroppingSlot`, use [`slot!()`].
211 ///
212 /// # Safety
213 ///
214 /// `ptr` must not be outlived by any other pointers to its allocation.
215 ///
216 /// `drop_flag`'s value must be dead, and must be a drop flag governing
217 /// the destruction of `ptr`'s storage in an appropriate manner as described
218 /// in [`moveit::drop_flag`][crate::drop_flag].
219 pub unsafe fn new_unchecked(
220 ptr: &'frame mut MaybeUninit<T>,
221 drop_flag: DropFlag<'frame>,
222 ) -> Self {
223 drop_flag.inc();
224 Self { ptr, drop_flag }
225 }
226
227 /// Put `val` into this slot, returning a reference to it.
228 pub fn put(self, val: T) -> (&'frame mut T, DropFlag<'frame>) {
229 ({ self.ptr }.write(val), self.drop_flag)
230 }
231
232 /// Pin `val` into this slot, returning a reference to it.
233 ///
234 /// # Safety
235 ///
236 /// This function pins the memory this slot wraps, but does not guarantee its
237 /// destructor is run; that is the caller's responsibility, by decrementing
238 /// the given [`DropFlag`].
239 pub unsafe fn pin(self, val: T) -> (Pin<&'frame mut T>, DropFlag<'frame>) {
240 self.emplace(new::of(val))
241 }
242
243 /// Emplace `new` into this slot, returning a reference to it.
244 ///
245 /// # Safety
246 ///
247 /// This function pins the memory this slot wraps, but does not guarantee its
248 /// destructor is run; that is the caller's responsibility, by decrementing
249 /// the given [`DropFlag`].
250 pub unsafe fn emplace<N: New<Output = T>>(
251 self,
252 new: N,
253 ) -> (Pin<&'frame mut T>, DropFlag<'frame>) {
254 match self.try_emplace(new) {
255 Ok((x, d)) => (x, d),
256 Err(e) => match e {},
257 }
258 }
259
260 /// Try to emplace `new` into this slot, returning a reference to it.
261 ///
262 /// # Safety
263 ///
264 /// This function pins the memory this slot wraps, but does not guarantee its
265 /// destructor is run; that is the caller's responsibility, by decrementing
266 /// the given [`DropFlag`].
267 pub unsafe fn try_emplace<N: TryNew<Output = T>>(
268 self,
269 new: N,
270 ) -> Result<(Pin<&'frame mut T>, DropFlag<'frame>), N::Error> {
271 self.drop_flag.inc();
272 new.try_new(Pin::new_unchecked(self.ptr))?;
273 Ok((
274 Pin::new_unchecked(self.ptr.assume_init_mut()),
275 self.drop_flag,
276 ))
277 }
278}
279
280#[doc(hidden)]
281#[allow(missing_docs)]
282pub mod __macro {
283 use super::*;
284 use crate::drop_flag::QuietFlag;
285 pub use core;
286
287 pub struct SlotDropper<T> {
288 val: MaybeUninit<T>,
289 drop_flag: QuietFlag,
290 }
291
292 impl<T> SlotDropper<T> {
293 #[allow(clippy::new_without_default)]
294 pub fn new() -> Self {
295 Self {
296 val: MaybeUninit::uninit(),
297 drop_flag: QuietFlag::new(),
298 }
299 }
300
301 // Workaround for `unsafe {}` unhygine wrt to lints.
302 //
303 // This function is still `unsafe`.
304 pub fn new_unchecked_hygine_hack(&mut self) -> DroppingSlot<T> {
305 unsafe {
306 DroppingSlot::new_unchecked(&mut self.val, self.drop_flag.flag())
307 }
308 }
309 }
310
311 impl<T> Drop for SlotDropper<T> {
312 fn drop(&mut self) {
313 if self.drop_flag.flag().is_dead() {
314 unsafe { ptr::drop_in_place(self.val.assume_init_mut()) }
315 }
316 }
317 }
318
319 // Workaround for `unsafe {}` unhygine wrt to lints.
320 //
321 // This function is still `unsafe`.
322 pub fn new_unchecked_hygine_hack<'frame, T>(
323 ptr: &'frame mut MaybeUninit<T>,
324 drop_flag: DropFlag<'frame>,
325 ) -> Slot<'frame, T> {
326 unsafe { Slot::new_unchecked(ptr, drop_flag) }
327 }
328}
329
330/// Constructs a new [`Slot`].
331///
332/// Because [`Slot`]s need to own data on the stack, but that data cannot
333/// move with the [`Slot`], it must be constructed using this macro. For
334/// example:
335/// ```
336/// moveit::slot!(x, y: bool);
337/// let x = x.put(5);
338/// let y = y.put(false);
339/// ```
340///
341/// This macro is especially useful for passing data into functions that want to
342/// emplace a value into the caller.
343///
344/// The `slot!(#[dropping] x)` syntax can be used to create a [`DroppingSlot`]
345/// instead. This should be a comparatively rare operation.
346///
347/// This macro can also be used without arguments to create a *temporary*
348/// [`Slot`]. Such types cannot be assigned to variables but can be used as
349/// part of a larger expression:
350///
351/// ```compile_fail
352/// # use moveit::Slot;
353/// let bad: Slot<i32> = moveit::slot!();
354/// bad.put(4); // Borrow check error.
355/// ```
356///
357/// ```
358/// # use moveit::Slot;
359/// fn do_thing(x: Slot<i32>) { /* ... */ }
360/// do_thing(moveit::slot!())
361/// ```
362#[macro_export]
363macro_rules! slot {
364 () => {
365 $crate::slot::__macro::new_unchecked_hygine_hack(
366 &mut $crate::slot::__macro::core::mem::MaybeUninit::uninit(),
367 $crate::drop_flag::TrappedFlag::new().flag(),
368 )
369 };
370 (#[dropping]) => {
371 $crate::slot::__macro::SlotDropper::new().new_unchecked_hygine_hack()
372 };
373 ($($name:ident $(: $ty:ty)?),* $(,)*) => {$(
374 let mut uninit = $crate::slot::__macro::core::mem::MaybeUninit::<
375 $crate::slot!(@tyof $($ty)?)
376 >::uninit();let trap = $crate::drop_flag::TrappedFlag::new();
377 let $name = $crate::slot::__macro::new_unchecked_hygine_hack(
378 &mut uninit,
379 trap.flag()
380 );
381 )*};
382 (#[dropping] $($name:ident $(: $ty:ty)?),* $(,)*) => {$(
383 let mut uninit = $crate::slot::__macro::SlotDropper::<
384 $crate::slot!(@tyof $($ty)?)
385 >::new();
386 #[allow(unsafe_code, unused_unsafe)]
387 let $name = uninit.new_unchecked_hygine_hack();
388 )*};
389 (@tyof) => {_};
390 (@tyof $ty:ty) => {$ty};
391}