shed/futures_lazy_shared/src/lib.rs (35 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under both the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree and the Apache
* License, Version 2.0 found in the LICENSE-APACHE file in the root directory
* of this source tree.
*/
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use futures::future::{FutureExt, Shared};
use once_cell::sync::OnceCell;
/// A lazily-initialized shared future
///
/// Like `futures::future::Shared`, except it can be lazily initialized,
/// potentially with a readily available value. This is useful when the
/// future might not be awaited on at all, or when sometimes the value is
/// already known at construction time. In both cases it prevents the
/// overhead of setting up the generator and boxing the shared future when it
/// is not needed.
#[derive(Clone)]
pub enum LazyShared<T>
where
T: Clone,
{
Ready(T),
Lazy(Arc<OnceCell<Shared<Pin<Box<dyn Future<Output = T> + Send>>>>>),
}
impl<T> LazyShared<T>
where
T: Clone,
{
/// Initialize the lazy-shared future with a ready value.
pub fn new_ready(value: T) -> Self {
LazyShared::Ready(value)
}
/// Initialize the lazy-shared future with no value.
pub fn new_empty() -> Self {
LazyShared::Lazy(Arc::new(OnceCell::new()))
}
/// Get the value of the shared future, providing an initialization
/// function for the shared future if it has not yet been initialized.
pub async fn get_or_init<F, Fut>(&self, init: F) -> T
where
F: FnOnce() -> Fut,
Fut: Future<Output = T> + Send + 'static,
{
match self {
LazyShared::Ready(value) => value.clone(),
LazyShared::Lazy(cell) => {
cell.get_or_init(move || init().boxed().shared())
.clone()
.await
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[tokio::test]
async fn test_ready() {
let count = Arc::new(AtomicUsize::new(0));
let lazy = LazyShared::new_ready(1u32);
let value = lazy
.get_or_init(|| {
let count = count.clone();
async move {
count.fetch_add(1, Ordering::Relaxed);
2u32
}
})
.await;
// Ready value is used, not lazy value.
assert_eq!(value, 1u32);
// Initializer was not called.
assert_eq!(count.load(Ordering::Relaxed), 0);
}
#[tokio::test]
async fn test_lazy() {
let count = Arc::new(AtomicUsize::new(0));
let lazy = LazyShared::new_empty();
let value = lazy
.get_or_init(|| {
let count = count.clone();
async move {
count.fetch_add(1, Ordering::Relaxed);
2u32
}
})
.await;
// Lazy value is used.
assert_eq!(value, 2u32);
// Initializer was called once.
assert_eq!(count.load(Ordering::Relaxed), 1);
// Read it again.
let value = lazy
.get_or_init(|| {
let count = count.clone();
async move {
count.fetch_add(1, Ordering::Relaxed);
3u32
}
})
.await;
// Original initializer value is used.
assert_eq!(value, 2u32);
// Initializer was not called again.
assert_eq!(count.load(Ordering::Relaxed), 1);
}
}