Generic RAII Object Pool

Reuse expensive objects through a mutex-backed pool whose guard returns the item automatically on drop, with a factory fallback.

RustResilience

Code

snippet
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex};

struct PoolInner<T> {
    items: Mutex<Vec<T>>,
    factory: Box<dyn Fn() -> T + Send + Sync>,
}

struct Pool<T> {
    inner: Arc<PoolInner<T>>,
}

struct PoolGuard<T> {
    inner: Arc<PoolInner<T>>,
    item: Option<T>,
}

impl<T> Pool<T> {
    fn new<F>(factory: F) -> Self
    where
        F: Fn() -> T + Send + Sync + 'static,
    {
        Self {
            inner: Arc::new(PoolInner {
                items: Mutex::new(Vec::new()),
                factory: Box::new(factory),
            }),
        }
    }

    fn acquire(&self) -> PoolGuard<T> {
        let item = self
            .inner
            .items
            .lock()
            .expect("pool mutex poisoned")
            .pop()
            .unwrap_or_else(|| (self.inner.factory)());
        PoolGuard {
            inner: Arc::clone(&self.inner),
            item: Some(item),
        }
    }

    fn available(&self) -> usize {
        self.inner
            .items
            .lock()
            .expect("pool mutex poisoned")
            .len()
    }
}

impl<T> Deref for PoolGuard<T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.item.as_ref().expect("guard item is present")
    }
}

impl<T> DerefMut for PoolGuard<T> {
    fn deref_mut(&mut self) -> &mut T {
        self.item.as_mut().expect("guard item is present")
    }
}

impl<T> Drop for PoolGuard<T> {
    fn drop(&mut self) {
        if let Some(item) = self.item.take() {
            if let Ok(mut items) = self.inner.items.lock() {
                items.push(item);
            }
        }
    }
}

fn main() {
    let pool = Pool::new(String::new);
    {
        let mut first = pool.acquire();
        first.push_str("hello");
    }
    let second = pool.acquire();
    println!(
        "reused={} available={}",
        !second.is_empty(),
        pool.available()
    );
}