fxfs/future_with_guard.rs
1// Copyright 2025 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
5use pin_project::pin_project;
6use std::future::Future;
7use std::pin::Pin;
8use std::task::{Context, Poll};
9
10/// A wrapper around a future and a guard object where the correctness of the future requires the
11/// guard object to be held while the future is alive.
12///
13/// This is equivalent to the below code but produces a future that is almost half the size of the
14/// future that rust generates: https://github.com/rust-lang/rust/issues/108906.
15/// ```rust
16/// let guard = acquire_guard();
17/// executor.spawn(async move {
18/// let _guard = guard;
19/// task.await;
20/// });
21/// ```
22#[pin_project]
23pub struct FutureWithGuard<T, F, R>
24where
25 F: Future<Output = R> + Send + 'static,
26 T: Send + 'static,
27{
28 // LINT.IfChange
29 #[pin]
30 future: F,
31 // LINT.ThenChange(//src/developer/debug/zxdb/console/commands/verb_async_backtrace.cc)
32 _object: T,
33}
34
35impl<T, F, R> FutureWithGuard<T, F, R>
36where
37 F: Future<Output = R> + Send + 'static,
38 T: Send + 'static,
39{
40 pub fn new(object: T, future: F) -> Self {
41 Self { future, _object: object }
42 }
43}
44
45impl<T, F, R> Future for FutureWithGuard<T, F, R>
46where
47 F: Future<Output = R> + Send + 'static,
48 T: Send + 'static,
49{
50 type Output = R;
51
52 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
53 self.project().future.poll(cx)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[fuchsia::test]
62 fn test_custom_future_is_smaller() {
63 async fn large_future() -> [u8; 128] {
64 let held_across_await = [0; 128];
65 std::future::pending::<()>().await;
66 held_across_await
67 }
68
69 let object = 10u64;
70 let task = large_future();
71 let custom_future = FutureWithGuard::new(object, task);
72 let custom_future_size = std::mem::size_of_val(&custom_future);
73
74 let object = 10u64;
75 let task = large_future();
76 let rust_future = async move {
77 let _object = object;
78 task.await;
79 };
80 let rust_future_size = std::mem::size_of_val(&rust_future);
81
82 // The large_future is 129 bytes:
83 // - 128 bytes for the array
84 // - 1 byte for the discriminant.
85 //
86 // The custom_future is 144 bytes:
87 // - 129 bytes for the large_future
88 // - 8 bytes for the u64 object
89 // - 7 bytes of padding
90 //
91 // The rust_future is 272 bytes:
92 // - 129 bytes to capture the large_future
93 // - 129 bytes to use the large_future
94 // - 8 bytes to capture the u64 object
95 // - 1 byte for the discriminant
96 // - 5 bytes of padding
97 //
98 // This assert only makes sure that the custom future is not bigger than the rust generated
99 // future.
100 //
101 // If this test starts failing while updating rust, the test can safely be disabled.
102 assert!(
103 custom_future_size <= rust_future_size,
104 "custom_future_size={custom_future_size} rust_future_size={rust_future_size}"
105 );
106 }
107}