replace_with/lib.rs
1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! `replace-with` provides the [`replace_with`] function.
6
7#![no_std]
8
9// Refer to `abort_on_panic` for details on why `std` is required.
10#[cfg(any(test, not(panic = "abort")))]
11extern crate std;
12
13use core::ptr;
14
15/// Uses `f` to replace the referent of `dst` with a new value.
16///
17/// Reads the current value in `dst`, calls `f` on that value, and overwrites
18/// `dst` using the new value returned by `f`. If `f` panics, the process is
19/// aborted.
20///
21/// This is useful for updating a value whose type is not [`Copy`].
22///
23/// # Examples
24///
25/// ```rust
26/// # use replace_with::replace_with;
27/// /// A value that might be stored on the heap (boxed) or on the stack (unboxed).
28/// pub enum MaybeBoxed<T> {
29/// Boxed(Box<T>),
30/// Unboxed(T),
31/// }
32///
33/// impl<T> MaybeBoxed<T> {
34/// /// Ensures that `self` is boxed, moving the value to the heap if necessary.
35/// pub fn ensure_boxed(&mut self) {
36/// replace_with(self, |m| match m {
37/// MaybeBoxed::Boxed(b) => MaybeBoxed::Boxed(b),
38/// MaybeBoxed::Unboxed(u) => MaybeBoxed::Boxed(Box::new(u)),
39/// })
40/// }
41/// }
42/// ```
43pub fn replace_with<T, F: FnOnce(T) -> T>(dst: &mut T, f: F) {
44 replace_with_and(dst, move |t| (f(t), ()))
45}
46
47/// Uses `f` to replace the referent of `dst` and returns a value from the
48/// transformation.
49///
50/// Like [`replace_with`] but the provided function returns a tuple of `(T, R`)
51/// where `T` is the new value for `dst` and `R` is returned from
52/// `replace_with_and`.
53pub fn replace_with_and<T, R, F: FnOnce(T) -> (T, R)>(dst: &mut T, f: F) -> R {
54 // This is not necessary today, but it may be necessary if the "strict
55 // pointer provenance" model [1] is adopted in the future.
56 //
57 // [1] https://github.com/rust-lang/rust/issues/95228
58 let dst = dst as *mut T;
59
60 // SAFETY:
61 // - The initial `ptr::read` is sound because `dst` is derived from a `&mut
62 // T`, and so all of `ptr::read`'s safety preconditions are satisfied:
63 // - `dst` is valid for reads
64 // - `dst` is properly aligned
65 // - `dst` points at a properly initialized value of type `T`
66 // - After `ptr::read` is called, we've created a copy of `*dst`. Since `T:
67 // !Copy`, it is not guaranteed that operating on both copies would be
68 // sound. Since we allow `f` to operate on `old`, we have to ensure that
69 // no code operates on `*dst`. This could happen in a few circumstances:
70 // - Code in this function could operate on `*dst`, which it doesn't.
71 // - Code in `f` could operate on `*dst`. Since `dst` is a mutable
72 // reference, and it is borrowed for the duration of this function call,
73 // `f` cannot also access `dst` (code that attempted to do that would
74 // fail to compile).
75 // - The caller could operate on `dst` after the function returns. There
76 // are two cases:
77 // - In the success case, `f` returns without panicking. It returns a
78 // new `T`, and we overwrite `*dst` with this new `T` using
79 // `ptr::write`. At this point, it is sound for code to operate on
80 // `*dst`, and so it is sound for this function to return.
81 // - In the failure case, `f` panics. Since, at the point we call `f`,
82 // we have not overwritten `*dst` yet, it would be unsound if the
83 // panic were to unwind the stack, allowing code from the caller to
84 // run. Since we call `f` within a call to `abort_on_panic`, we are
85 // guaranteed that the process would abort, and no future code could
86 // run.
87 // - The call to `ptr::write` itself is sound because, thanks to `dst`
88 // being derived from a `&mut T`, all of `ptr::write`'s preconditions are
89 // satisfied:
90 // - `dst` is valid for writes
91 // - `dst` is properly aligned
92 unsafe {
93 let old = ptr::read(dst);
94 let (new, ret) = abort_on_panic(move || f(old));
95 ptr::write(dst, new);
96 ret
97 }
98}
99
100/// Calls `f` or aborts the process if `f` panics.
101fn abort_on_panic<T, F: FnOnce() -> T>(f: F) -> T {
102 #[cfg(panic = "abort")]
103 {
104 // When the panic strategy is "abort", any panic will immediately abort
105 // the process. There is no unwinding, so the drop bomb is unnecessary.
106 f()
107 }
108 #[cfg(not(panic = "abort"))]
109 {
110 use core::mem;
111
112 struct CallOnDrop<O, F: Fn() -> O>(F);
113 impl<O, F: Fn() -> O> Drop for CallOnDrop<O, F> {
114 #[cold]
115 fn drop(&mut self) {
116 (self.0)();
117 }
118 }
119
120 // NOTE: If the panic strategy is not "abort", a panic will attempt to
121 // unwind the stack, which would leave `dst` uninitialized and violate
122 // memory safety. To prevent this, we must abort the process on drop.
123 // Doing so with a backtrace requires `std`. In a `no_std` environment,
124 // we cannot ensure safety if panics can unwind without `std` support.
125 let backtrace_and_abort_on_drop = CallOnDrop(#[inline(never)] || {
126 // SAFETY: This guard ensures that we abort in both of the following
127 // two cases:
128 // - The code executes normally (the guard is dropped at the end of
129 // the function)
130 // - The backtrace code panics (the guard is dropped during
131 // unwinding)
132 //
133 // No functions called from the backtrace code are documented to
134 // panic, but this serves as a hedge in case there are undocumented
135 // panic conditions.
136 let abort_on_drop = CallOnDrop(std::process::abort);
137
138 use std::io::Write as _;
139 let backtrace = std::backtrace::Backtrace::force_capture();
140 let mut stderr = std::io::stderr().lock();
141 // We treat backtrace-printing as best-effort, so we ignore any
142 // errors.
143 let _ =
144 write!(&mut stderr, "replace_with: callback panicked; backtrace:\n{backtrace}\n");
145 let _ = stderr.flush();
146
147 mem::drop(abort_on_drop);
148 });
149
150 let t = f();
151 mem::forget(backtrace_and_abort_on_drop);
152 t
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_replace_with() {
162 let mut x = 1usize;
163 replace_with(&mut x, |x| x * 2);
164 assert_eq!(x, 2);
165 }
166}