Implementing an object factory pattern in rust using closures

Coming from a Java background, I’m used to using the object factory pattern to inject behavior into an existing component. For this tutorial we’ll use the example of building a connection pool that can be used with different types of database connections.

Disclaimer: The entire approach taken here might not be the best way of doing things in Rust at all, but I made it work with some help from the Rust IRC channel (shout out to Quxxy!). If you are a Rust guru and know of a more idiomatic way of solving this problem I would love to hear about it!

Anyway, without further ado, here is our naive connection pool class. Oops, did I just say “class”? I meant “struct”. It’s hard to undo so much Java.

struct ConnectionPool<F, T> where F: Fn() -> T {
    create_conn: F,
    pool: Vec<T>
}

This struct uses generics so we could create instances of this connection pool using different implementations of T (the connection type) although F must always be a function that has no arguments and returns a T.

The struct stores the F closure for creating new connections and has a Vec<T> to hold the connections.

Next, we implement some methods on this struct.

impl<F, T> ConnectionPool<F, T> where F: Fn() -> T {

    fn new(create_conn: F) -> Self {
        let mut pool = vec![];
        ConnectionPool { create_conn: create_conn, pool: pool }
    }

    fn borrow_conn(&mut self) -> Option<T> {
        if self.pool.len() == 0 {
            self.pool.push((self.create_conn)())
        }
        self.pool.pop()
    }

    fn return_conn(&mut self, conn: T) {
        self.pool.push(conn);
    }
}

Ignoring the dubious logic in here (luckily, we’re not trying to build a real connection pool) the main point of interest is at line 10 where we create new connections by invoking the closure using the syntax (self.create_conn)().

Now that the generic connection pool is defined, we can go ahead and define our concrete connection struct that we want to store in the pool.

struct OracleConnection;

impl OracleConnection {
    fn new() -> OracleConnection {
        OracleConnection
    }
    fn query(&self, sql: &str) { 
        println!("Running Oracle query: {}", sql) 
    }
    fn close(&self) { 
        println!("Closing an Oracle connection") 
    }
}

And finally, we can implement some code to create and use the connection pool.

let mut pool = ConnectionPool::new(OracleConnection::new);
let conn = pool.borrow_conn().unwrap();
conn.query("SELECT * FROM customer LIMIT 1");

For the closure that we pass to the ConnectionPool::new() method, we are just passing a reference to a function. It would have been possible to use closure syntax instead e.g. ConnectionPool::new(|| OracleConnection {}) but using the function is much cleaner.

Source code for this tutorial is available here.

Resources

Recommended Reading

Recent Posts

Sponsored