Skip to main content

windows_thread_ambient_sys/
capture_set.rs

1// Copyright (c) Mike Grier.
2
3//! Which capturable aspects to collect.
4//!
5//! A capture set names the aspects that can be **read** off the calling thread.
6//! Declared aspects are not in this vocabulary at all: there is nothing to
7//! collect for them, so they are stated by the caller instead -- see
8//! [`crate::declared`].
9//!
10//! # There is deliberately no `Default` implementation
11//!
12//! The default set is a **named constant**, [`CaptureSet::DEFAULT`], which a
13//! caller must name to get.
14//!
15//! That is not ceremony. The workspace decision that this composite is
16//! exhaustively enumerated rests on its field list being contract surface: a
17//! silently added field is a silent semantic change. An implicit default has the
18//! same property in a worse form, because growing it changes behaviour for
19//! callers who never named it and have no diff to review. Naming it makes
20//! growth a visible change to a named thing, lets a caller who wants stability
21//! list aspects explicitly, and gives a caller who takes the default somewhere
22//! to go and read what it contains.
23//!
24//! # Example
25//!
26//! ```
27//! use windows_thread_ambient_sys::capture_set::{CaptureSet, CapturableAspect};
28//!
29//! // The default is opted into by name, never inherited by accident.
30//! let set = CaptureSet::DEFAULT;
31//! assert!(set.contains(CaptureSet::IMPERSONATION));
32//!
33//! // TxF is excluded from the default; ask for it deliberately.
34//! assert!(!set.contains(CaptureSet::TRANSACTION));
35//! let with_txf = set.union(CaptureSet::TRANSACTION);
36//! assert!(with_txf.contains(CaptureSet::TRANSACTION));
37//!
38//! // A caller that wants stability names its aspects rather than taking a set
39//! // whose membership may grow.
40//! let pinned = CaptureSet::IMPERSONATION.union(CaptureSet::ERROR_MODE);
41//! assert_eq!(pinned.aspects().count(), 2);
42//! assert!(pinned.aspects().any(|a| a == CapturableAspect::ErrorMode));
43//! ```
44
45use std::fmt;
46
47/// The bit each aspect occupies in a [`CaptureSet`].
48///
49/// Internal to this crate and not a wire format, so the values carry no
50/// compatibility obligation; they exist so no bare literal appears in the logic.
51mod bit {
52    pub(super) const IMPERSONATION: u8 = 1 << 0;
53    pub(super) const ERROR_MODE: u8 = 1 << 1;
54    pub(super) const TRANSACTION: u8 = 1 << 2;
55}
56
57/// One aspect that can be read off the calling thread.
58///
59/// Declared aspects are absent by construction: there is nothing to capture for
60/// them.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62#[non_exhaustive]
63pub enum CapturableAspect {
64    /// The thread's impersonation context.
65    Impersonation,
66    /// The thread error mode.
67    ErrorMode,
68    /// The thread's current TxF transaction.
69    Transaction,
70}
71
72impl CapturableAspect {
73    /// Every capturable aspect.
74    ///
75    /// [`CaptureSet::ALL`] is derived from this list rather than restating it,
76    /// so an aspect added here joins that set automatically instead of leaving
77    /// it quietly stale.
78    pub const EVERY: &'static [Self] = &[Self::Impersonation, Self::ErrorMode, Self::Transaction];
79
80    const fn bit(self) -> u8 {
81        match self {
82            Self::Impersonation => bit::IMPERSONATION,
83            Self::ErrorMode => bit::ERROR_MODE,
84            Self::Transaction => bit::TRANSACTION,
85        }
86    }
87
88    /// The singleton set containing just this aspect.
89    #[must_use]
90    pub const fn as_set(self) -> CaptureSet {
91        CaptureSet { bits: self.bit() }
92    }
93}
94
95impl fmt::Display for CapturableAspect {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(match self {
98            Self::Impersonation => "impersonation",
99            Self::ErrorMode => "error mode",
100            Self::Transaction => "transaction",
101        })
102    }
103}
104
105/// Which capturable aspects a capture should collect.
106///
107/// There is no `Default` implementation; see the module documentation.
108#[derive(Clone, Copy, PartialEq, Eq, Hash)]
109pub struct CaptureSet {
110    bits: u8,
111}
112
113const fn derive_all() -> CaptureSet {
114    let mut bits = 0u8;
115    let mut index = 0;
116    while index < CapturableAspect::EVERY.len() {
117        bits |= CapturableAspect::EVERY[index].bit();
118        index += 1;
119    }
120    CaptureSet { bits }
121}
122
123impl CaptureSet {
124    /// Collect nothing.
125    ///
126    /// Every aspect is then [`Captured::NotCaptured`](crate::Captured::NotCaptured),
127    /// which leaves the target thread's own values alone.
128    pub const NONE: Self = Self { bits: 0 };
129
130    /// Just the impersonation context.
131    pub const IMPERSONATION: Self = CapturableAspect::Impersonation.as_set();
132
133    /// Just the thread error mode.
134    pub const ERROR_MODE: Self = CapturableAspect::ErrorMode.as_set();
135
136    /// Just the current TxF transaction.
137    pub const TRANSACTION: Self = CapturableAspect::Transaction.as_set();
138
139    /// The recommended starting point: impersonation and the thread error mode.
140    ///
141    /// **Adding an aspect to this set is a breaking change**, and that is the
142    /// reason it exists as a name rather than as a `Default` implementation.
143    ///
144    /// TxF is deliberately excluded. It is deprecated by Microsoft, capturing it
145    /// costs a lazy `ntdll` binding a caller may never need, and -- the reason
146    /// that actually decides it -- a captured transaction enlists remoted work
147    /// in a transaction the caller may commit or roll back while that work is
148    /// still running. That is a hazard to opt into deliberately, not one to
149    /// acquire by taking a default. Add [`TRANSACTION`](Self::TRANSACTION) when
150    /// you mean it.
151    pub const DEFAULT: Self = Self {
152        bits: bit::IMPERSONATION | bit::ERROR_MODE,
153    };
154
155    /// Every capturable aspect this version knows.
156    ///
157    /// **This set grows.** Membership is its meaning, so a later version adding
158    /// an aspect will capture it here without further notice. A caller that
159    /// needs a fixed set should name its aspects instead.
160    pub const ALL: Self = derive_all();
161
162    /// Both sets' aspects.
163    #[must_use]
164    pub const fn union(self, other: Self) -> Self {
165        Self {
166            bits: self.bits | other.bits,
167        }
168    }
169
170    /// This set without `other`'s aspects.
171    #[must_use]
172    pub const fn without(self, other: Self) -> Self {
173        Self {
174            bits: self.bits & !other.bits,
175        }
176    }
177
178    /// Whether every aspect of `other` is present.
179    #[must_use]
180    pub const fn contains(self, other: Self) -> bool {
181        self.bits & other.bits == other.bits
182    }
183
184    /// Whether nothing would be collected.
185    #[must_use]
186    pub const fn is_empty(self) -> bool {
187        self.bits == 0
188    }
189
190    /// The aspects in this set, in a stable order.
191    pub fn aspects(self) -> impl Iterator<Item = CapturableAspect> {
192        CapturableAspect::EVERY
193            .iter()
194            .copied()
195            .filter(move |aspect| self.bits & aspect.bit() != 0)
196    }
197}
198
199impl fmt::Debug for CaptureSet {
200    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201        if self.is_empty() {
202            return f.write_str("CaptureSet(none)");
203        }
204        f.write_str("CaptureSet(")?;
205        for (index, aspect) in self.aspects().enumerate() {
206            if index > 0 {
207                f.write_str(", ")?;
208            }
209            write!(f, "{aspect}")?;
210        }
211        f.write_str(")")
212    }
213}
214
215impl From<CapturableAspect> for CaptureSet {
216    fn from(aspect: CapturableAspect) -> Self {
217        aspect.as_set()
218    }
219}
220
221#[cfg(test)]
222mod tests;