use alloc::boxed::Box;
use alloc::vec::Vec;
use core::future::Future;
use core::marker::PhantomData;
use core::pin::Pin;
use super::fibra::{Fibra, FibraExitus};
pub struct Res<A> {
acquire: Box<dyn FnOnce() -> A + Send>,
release: Box<dyn FnOnce(A) + Send>,
}
struct Custos<A, Rel: FnOnce(A)> {
resource: Option<A>,
release: Option<Rel>,
}
impl<A, Rel: FnOnce(A)> Drop for Custos<A, Rel> {
fn drop(&mut self) {
if let (Some(resource), Some(release)) = (self.resource.take(), self.release.take()) {
release(resource);
}
}
}
impl<A> Res<A> {
#[inline]
pub fn make<Acq, Rel>(acquire: Acq, release: Rel) -> Self
where
Acq: FnOnce() -> A + Send + 'static,
Rel: FnOnce(A) + Send + 'static,
{
Res {
acquire: Box::new(acquire),
release: Box::new(release),
}
}
#[inline]
pub fn purus(value: A) -> Self
where
A: Send + 'static,
{
Res {
acquire: Box::new(move || value),
release: Box::new(|_| {}),
}
}
#[inline]
pub fn uti<B, F>(self, f: F) -> B
where
F: FnOnce(&A) -> B,
{
let custos = Custos {
resource: Some((self.acquire)()),
release: Some(self.release),
};
let result = f(custos
.resource
.as_ref()
.expect("resource is held until the guard drops"));
drop(custos);
result
}
#[inline]
pub fn use_res<B, F>(self, f: F) -> B
where
F: FnOnce(&A) -> B,
{
self.uti(f)
}
#[inline]
pub fn uti_mut<B, F>(self, f: F) -> B
where
F: FnOnce(&mut A) -> B,
{
let mut custos = Custos {
resource: Some((self.acquire)()),
release: Some(self.release),
};
let result = f(custos
.resource
.as_mut()
.expect("resource is held until the guard drops"));
drop(custos);
result
}
#[inline]
pub fn mutare<B, F, G>(self, f: F, g: G) -> Res<B>
where
F: FnOnce(A) -> B + Send + 'static,
G: FnOnce(B) -> A + Send + 'static,
A: 'static,
B: Send + 'static,
{
let Res { acquire, release } = self;
Res {
acquire: Box::new(move || f((acquire)())),
release: Box::new(move |b| (release)(g(b))),
}
}
}
#[inline]
pub fn zip_res<A, B>(ra: Res<A>, rb: Res<B>) -> Res<(A, B)>
where
A: Send + 'static,
B: Send + 'static,
{
let Res {
acquire: acq_a,
release: rel_a,
} = ra;
let Res {
acquire: acq_b,
release: rel_b,
} = rb;
Res {
acquire: Box::new(move || {
let a = (acq_a)();
let b = (acq_b)();
(a, b)
}),
release: Box::new(move |(a, b)| {
(rel_b)(b);
(rel_a)(a);
}),
}
}
#[inline]
pub fn zip_all_res<A>(resources: Vec<Res<A>>) -> Res<Vec<A>>
where
A: Send + 'static,
{
if resources.is_empty() {
return Res::purus(Vec::new());
}
let n = resources.len();
let mut acquirers: Vec<Box<dyn FnOnce() -> A + Send>> = Vec::with_capacity(n);
let mut releasers: Vec<Box<dyn FnOnce(A) + Send>> = Vec::with_capacity(n);
for res in resources {
acquirers.push(res.acquire);
releasers.push(res.release);
}
Res {
acquire: Box::new(move || acquirers.into_iter().map(|acq| acq()).collect()),
release: Box::new(move |items: Vec<A>| {
for (item, rel) in items.into_iter().zip(releasers).rev() {
rel(item);
}
}),
}
}
type AsyncAcquireFn<A> = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = A> + Send>> + Send>;
type AsyncReleaseFn<A> = Box<dyn FnOnce(A) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>;
pub struct ResAsync<A> {
acquire: AsyncAcquireFn<A>,
_release: AsyncReleaseFn<A>,
}
impl<A> ResAsync<A> {
#[inline]
pub fn make<Acq, AcqFut, Rel, RelFut>(acquire: Acq, release: Rel) -> Self
where
Acq: FnOnce() -> AcqFut + Send + 'static,
AcqFut: Future<Output = A> + Send + 'static,
Rel: FnOnce(A) -> RelFut + Send + 'static,
RelFut: Future<Output = ()> + Send + 'static,
{
ResAsync {
acquire: Box::new(move || Box::pin(acquire())),
_release: Box::new(move |a| Box::pin(release(a))),
}
}
#[inline]
pub fn purus(value: A) -> Self
where
A: Send + 'static,
{
ResAsync {
acquire: Box::new(move || Box::pin(async move { value })),
_release: Box::new(|_| Box::pin(async {})),
}
}
#[inline]
pub fn from_sync(res: Res<A>) -> Self
where
A: Send + 'static,
{
let Res { acquire, release } = res;
ResAsync {
acquire: Box::new(move || Box::pin(async move { (acquire)() })),
_release: Box::new(move |a| Box::pin(async move { (release)(a) })),
}
}
#[inline]
pub fn uti<B, F, Fut>(self, f: F) -> Fibra<B>
where
F: FnOnce(A) -> Fut + Send + 'static,
Fut: Future<Output = B> + Send + 'static,
A: Send + 'static,
B: Send + 'static,
{
Fibra::new(async move {
let resource = (self.acquire)().await;
f(resource).await
})
}
#[inline]
pub fn uti_safe<B, F, Fut>(self, f: F) -> Fibra<B>
where
F: FnOnce(A) -> Fut + Send + 'static,
Fut: Future<Output = FibraExitus<B>> + Send + 'static,
A: Send + 'static,
B: Send + 'static,
{
Fibra::new(async move {
let resource = (self.acquire)().await;
f(resource).await
})
.flat_map(|res_b| match res_b {
Ok(b) => Fibra::purus(b),
Err(e) => Fibra::deficere(e),
})
}
}
#[inline]
pub fn amplexus<A, B, Acq, Use, Rel>(acquire: Acq, use_fn: Use, release: Rel) -> B
where
Acq: FnOnce() -> A,
Use: FnOnce(&A) -> B,
Rel: FnOnce(A),
{
let custos = Custos {
resource: Some(acquire()),
release: Some(release),
};
let result = use_fn(
custos
.resource
.as_ref()
.expect("resource is held until the guard drops"),
);
drop(custos);
result
}
#[inline]
pub fn bracket<A, B, Acq, Use, Rel>(acquire: Acq, use_fn: Use, release: Rel) -> B
where
Acq: FnOnce() -> A,
Use: FnOnce(&A) -> B,
Rel: FnOnce(A),
{
amplexus(acquire, use_fn, release)
}
#[inline]
pub fn amplexus_async<A, B, Acq, AcqFut, Use, UseFut, Rel, RelFut>(
acquire: Acq,
use_fn: Use,
release: Rel,
) -> Fibra<B>
where
Acq: FnOnce() -> AcqFut + Send + 'static,
AcqFut: Future<Output = A> + Send + 'static,
Use: FnOnce(A) -> UseFut + Send + 'static,
UseFut: Future<Output = (A, B)> + Send + 'static,
Rel: FnOnce(A) -> RelFut + Send + 'static,
RelFut: Future<Output = ()> + Send + 'static,
A: Send + 'static,
B: Send + 'static,
{
Fibra::new(async move {
let resource = acquire().await;
let (resource, result) = use_fn(resource).await;
release(resource).await;
result
})
}
#[inline]
pub fn bracket_async<A, B, Acq, AcqFut, Use, UseFut, Rel, RelFut>(
acquire: Acq,
use_fn: Use,
release: Rel,
) -> Fibra<B>
where
Acq: FnOnce() -> AcqFut + Send + 'static,
AcqFut: Future<Output = A> + Send + 'static,
Use: FnOnce(A) -> UseFut + Send + 'static,
UseFut: Future<Output = (A, B)> + Send + 'static,
Rel: FnOnce(A) -> RelFut + Send + 'static,
RelFut: Future<Output = ()> + Send + 'static,
A: Send + 'static,
B: Send + 'static,
{
amplexus_async(acquire, use_fn, release)
}
#[inline]
pub fn finaliter<A, F, Fin>(computation: F, finalizer: Fin) -> A
where
F: FnOnce() -> A,
Fin: FnOnce(),
{
let custos = Custos {
resource: Some(()),
release: Some(move |()| finalizer()),
};
let result = computation();
drop(custos);
result
}
#[inline]
pub fn finaliter_async<A, F, FFut, Fin, FinFut>(computation: F, finalizer: Fin) -> Fibra<A>
where
F: FnOnce() -> FFut + Send + 'static,
FFut: Future<Output = A> + Send + 'static,
Fin: FnOnce() -> FinFut + Send + 'static,
FinFut: Future<Output = ()> + Send + 'static,
A: Send + 'static,
{
Fibra::new(async move {
let result = computation().await;
finalizer().await;
result
})
}
pub struct Piscina<A> {
create: Box<dyn Fn() -> A + Send + Sync>,
destroy: Box<dyn Fn(A) + Send + Sync>,
max_size: usize,
_marker: PhantomData<A>,
}
impl<A> Piscina<A> {
#[inline]
pub fn new<C, D>(create: C, destroy: D, max_size: usize) -> Self
where
C: Fn() -> A + Send + Sync + 'static,
D: Fn(A) + Send + Sync + 'static,
{
Piscina {
create: Box::new(create),
destroy: Box::new(destroy),
max_size,
_marker: PhantomData,
}
}
#[inline]
pub fn max_size(&self) -> usize {
self.max_size
}
#[inline]
pub fn create_resource(&self) -> A {
(self.create)()
}
#[inline]
pub fn destroy_resource(&self, resource: A) {
(self.destroy)(resource);
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::String;
use alloc::vec;
#[test]
fn test_res_make_and_use() {
let _acquired = core::cell::Cell::new(false);
let _released = core::cell::Cell::new(false);
let res = Res::make(|| 42, |_| {});
let result = res.use_res(|n| *n * 2);
assert_eq!(result, 84);
}
#[test]
fn test_res_purus() {
let res = Res::purus(42);
let result = res.use_res(|n| *n + 1);
assert_eq!(result, 43);
}
#[test]
fn test_zip_res() {
let ra = Res::purus(1);
let rb = Res::purus(2);
let combined = zip_res(ra, rb);
let result = combined.use_res(|(a, b)| *a + *b);
assert_eq!(result, 3);
}
#[test]
fn test_zip_all_res_empty() {
let resources: Vec<Res<i32>> = vec![];
let combined = zip_all_res(resources);
let result = combined.use_res(std::vec::Vec::len);
assert_eq!(result, 0);
}
#[test]
fn test_bracket() {
let result = bracket(|| 42, |n| *n * 2, |_| {});
assert_eq!(result, 84);
}
#[test]
fn test_finaliter() {
let mut finalized = false;
let result = finaliter(
|| 42,
|| {
finalized = true;
},
);
assert_eq!(result, 42);
assert!(finalized);
}
#[test]
fn test_piscina_new() {
let pool: Piscina<i32> = Piscina::new(|| 0, |_| {}, 10);
assert_eq!(pool.max_size(), 10);
}
#[test]
fn test_piscina_create_destroy() {
let pool: Piscina<String> = Piscina::new(|| String::from("resource"), |_| {}, 5);
let res = pool.create_resource();
assert_eq!(res, "resource");
pool.destroy_resource(res); }
#[test]
fn test_res_uti_releases_on_panic() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
let released = Arc::new(AtomicBool::new(false));
let released_clone = Arc::clone(&released);
let res = Res::make(|| 42, move |_| released_clone.store(true, Ordering::SeqCst));
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
res.uti(|_| -> i32 { panic!("boom") })
}));
assert!(outcome.is_err());
assert!(
released.load(Ordering::SeqCst),
"release must run even when `f` panics"
);
}
#[test]
fn test_res_uti_mut_releases_on_panic() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
let released = Arc::new(AtomicBool::new(false));
let released_clone = Arc::clone(&released);
let res = Res::make(|| 42, move |_| released_clone.store(true, Ordering::SeqCst));
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
res.uti_mut(|_| -> i32 { panic!("boom") })
}));
assert!(outcome.is_err());
assert!(
released.load(Ordering::SeqCst),
"release must run even when `f` panics"
);
}
#[test]
fn test_amplexus_releases_on_panic() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
let released = Arc::new(AtomicBool::new(false));
let released_clone = Arc::clone(&released);
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
amplexus(
|| 42,
|_: &i32| -> i32 { panic!("boom") },
move |_| released_clone.store(true, Ordering::SeqCst),
)
}));
assert!(outcome.is_err());
assert!(
released.load(Ordering::SeqCst),
"release must run even when `use_fn` panics"
);
}
#[test]
fn test_finaliter_releases_on_panic() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
let finalized = Arc::new(AtomicBool::new(false));
let finalized_clone = Arc::clone(&finalized);
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
finaliter(
|| -> i32 { panic!("boom") },
move || finalized_clone.store(true, Ordering::SeqCst),
)
}));
assert!(outcome.is_err());
assert!(
finalized.load(Ordering::SeqCst),
"finalizer must run even when `computation` panics"
);
}
#[test]
fn test_amplexus_async_release_gets_same_resource_not_clone() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicUsize, Ordering};
use core::task::{Context, Poll};
struct Marker(#[allow(dead_code)] u64);
fn drive<F: Future>(fut: F) -> F::Output {
let mut fut = core::pin::pin!(fut);
let mut cx = Context::from_waker(core::task::Waker::noop());
loop {
if let Poll::Ready(out) = fut.as_mut().poll(&mut cx) {
return out;
}
}
}
let resource = Box::new(Marker(0xC0_FFEE));
let acquired_addr = core::ptr::from_ref::<Marker>(Box::as_ref(&resource)) as usize;
let released_addr = Arc::new(AtomicUsize::new(0));
let released_addr_clone = Arc::clone(&released_addr);
let fiber = amplexus_async(
move || async move { resource },
|res: Box<Marker>| async move { (res, ()) },
move |res: Box<Marker>| {
released_addr_clone.store(
core::ptr::from_ref::<Marker>(Box::as_ref(&res)) as usize,
Ordering::SeqCst,
);
async move { drop(res) }
},
);
let outcome = drive(fiber);
assert!(outcome.is_ok());
assert_eq!(
released_addr.load(Ordering::SeqCst),
acquired_addr,
"release must receive the exact resource acquire produced, not a clone"
);
}
}