DEV Community

Cover image for Generic MongoDB function in Rust
Jorge Castro
Jorge Castro

Posted on • Edited on

Generic MongoDB function in Rust

Hello guys!. As an Android Developer 🤖❤️, I find myself learning Rust and it seems more and more entertaining to play in my spare time with this language. Today I want to write a bit and this time we will create a small utility to work with MongoDB cursors in Rust in a generic way.

While having fun with Rust and doing a couple of queries with MongoDB I realized that I was repeating the same pattern in different places: a listing of N things that follow an identical query structure in MongoDB. Giving a little more context, when we want to retrieve a set of data from a Mongo collection, we get a cursor of the type of data we want. Based on that scenario, I wanted to create a small function that would help me convert that cursor to an array of data of type T, where T is a generic type; that is, when calling my function T, it will take the specific type that it needs at that moment.

Generic Data Types

We use generics to create definitions for items like function signatures or structs, which we can then use with many different concrete data types.

you can see more about the generic data types here.

Let’s get to the point

First of all, I want to start from the fact that it is not a tutorial that starts from scratch, I am assuming that you know and understand the most basic concepts of Rust such as: how to create a project, functions and modules in Rust, among other small concepts . Having said all of the above, let’s continue with the article.

We’ll start by defining our generic function that will receive a generic cursor Cursor::<T> and return a data set Vec<T>. To define our generic function we will create a module mod.rs (You name it to your liking). For this example we will call our function extract_data and add the code below.

use mongodb::Cursor;
use futures::stream::StreamExt;
use rocket::serde::DeserializeOwned;

pub async fn extract_data<T: DeserializeOwned + Unpin + Send + Sync>(mut cursor: Cursor::<T>) -> Vec<T> {
    let mut result = Vec::new();
    while let Some(doc) = cursor.next().await {
        let item = doc.unwrap();
        result.push(item)
    }
    result
}
Enter fullscreen mode Exit fullscreen mode

Let’s create a small function to get a list of users. Don’t focus on the content of the function; surely you can make better code. We will call this function get_users_cursor.

async fn get_users_cursor() -> Cursor<UserModel> {
  let mut client_options = ClientOptions::parse(DATABASE_URI)?;
  client_options.app_name = Some(APP_NAME.to_string());
  let client = Client::with_options(client_options)?;
  let database = client.database(DATABASE_NAME);

  let collection: Collection<UserModel> = client.database(DATABASE_NAME)
    .collection::<UserModel>("users");

  collection.find(None, None).await.unwrap()
}
Enter fullscreen mode Exit fullscreen mode

Finally, it’s time to call our generic function to obtain a dataset of the generic type, which in our example is UserModel.

pub async fn fetch_detail(&self) -> Vec<UserModel> {
  let cursor = self.get_users_cursor().await;
  extract_data::<UserModel>(cursor).await
}
Enter fullscreen mode Exit fullscreen mode

Remember to import the module path where you defined the extract_data generic function.

If you like my content and want to support my work, you can give me a cup of coffee ☕️ 🥰

Ko-fi

Buy me a coffee

Follow me in

Twitter: @devjcastro

Top comments (0)