tinymist_std/concepts/
query.rs

1use core::fmt;
2use std::sync::OnceLock;
3
4use parking_lot::Mutex;
5
6/// Represents a reference to some lazily executed query.
7/// The compute function should be pure enough during call the [`compute`] and
8/// [`compute_with_context`] so that the query result is consistent through any
9/// implementations (the provided `f`).
10///
11/// [`compute`]: Self::compute
12/// [`compute_with_context`]: Self::compute_with_context
13pub struct QueryRef<Res, Err, QueryContext = ()> {
14    ctx: Mutex<Option<QueryContext>>,
15    /// `None` means no value has been computed yet.
16    cell: OnceLock<Result<Res, Err>>,
17}
18
19impl<T, E, QC> QueryRef<T, E, QC> {
20    /// Creates a new query reference with the given value.
21    pub fn with_value(value: T) -> Self {
22        let cell = OnceLock::new();
23        cell.get_or_init(|| Ok(value));
24        Self {
25            ctx: Mutex::new(None),
26            cell,
27        }
28    }
29
30    /// Creates a new query reference with the given context to execute the
31    /// query.
32    pub fn with_context(ctx: QC) -> Self {
33        Self {
34            ctx: Mutex::new(Some(ctx)),
35            cell: OnceLock::new(),
36        }
37    }
38}
39
40impl<T, E: Clone, QC> QueryRef<T, E, QC> {
41    /// Computes and return a checked reference guard.
42    #[inline]
43    pub fn compute<F: FnOnce() -> Result<T, E>>(&self, f: F) -> Result<&T, E> {
44        self.compute_with_context(|_| f())
45    }
46
47    /// Computes with context and return a checked reference guard.
48    #[inline]
49    pub fn compute_with_context<F: FnOnce(QC) -> Result<T, E>>(&self, f: F) -> Result<&T, E> {
50        let result = self.cell.get_or_init(|| f(self.ctx.lock().take().unwrap()));
51        result.as_ref().map_err(Clone::clone)
52    }
53
54    /// Gets the reference to the (maybe uninitialized) result.
55    ///
56    /// Returns `None` if the cell is empty, or being initialized. This
57    /// method never blocks.
58    ///
59    /// It is possible not hot, so that it is non-inlined
60    pub fn get_uninitialized(&self) -> Option<&Result<T, E>> {
61        self.cell.get()
62    }
63}
64
65impl<T, E> Default for QueryRef<T, E> {
66    fn default() -> Self {
67        QueryRef {
68            ctx: Mutex::new(Some(())),
69            cell: OnceLock::new(),
70        }
71    }
72}
73
74impl<T, E, QC> fmt::Debug for QueryRef<T, E, QC>
75where
76    T: fmt::Debug,
77    E: fmt::Debug,
78    QC: fmt::Debug,
79{
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        let ctx = self.ctx.lock();
82        let res = self.cell.get();
83        f.debug_struct("QueryRef")
84            .field("context", &ctx)
85            .field("result", &res)
86            .finish()
87    }
88}