Recommended Rust Way To Store Mongo "Client" Object?

I've used mongo a lot in the past using Typescript / Node.Js libraries, and it was great.

As a toy project I am building a little cli tool mongo CRUD kind of project. In my node projects I would create one initial "Client" object with the mongo uri string, it would establish the mongo connection, and then all my queries would execute very fast when I call them later.

In the Rust world though creating these kinds of global static things is a huge "unsafe" no-no usually... but maybe if the Client object itself is immutable then it's ok? Anyway, I'm wondering if anyone has any example of an effective way of doing this in Rust that doesn't require unsafe code...

For reference, here is some example code that connects to mongodb and gets a collection, but I'm still not sure "where to put" the client object (or maybe the db object?) so that it can be reused for future calls and not immediately destroyed... should I use some kind of mutex wrapper around the thing? where in your project would you put that?

use mongodb::{Client, options::ClientOptions};
// Parse a connection string into an options struct.
let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?;

// Manually set an option.
client_options.app_name = Some("My App".to_string());

// Get a handle to the deployment.
let client = Client::with_options(client_options)?;

// List the names of the databases in that deployment.
for db_name in client.list_database_names(None, None).await? {
    println!("{}", db_name);
}

// Get a handle to a database.
let db = client.database("mydb");

// List the names of the collections in that database.
for collection_name in db.list_collection_names(None).await? {
    println!("{}", collection_name);
}

In Rust you would wrap the singleton in an Arc, for example.

Just Clone the client and move the clone wherever you need it. It contains an Arc, so it's very cheap to clone and clones will point to the identical client state internally. This is in the official documentation of the crate.

In Rust it's generally easiest to just pass the value into the functions that need it.

If you share a little more about how you're structuring your project people might have more concrete suggestions, but generally when I'm building a CLI type thing here's how I tend to set things up.

#![allow(unused)]

use clap::Parser;

struct Connection;

#[derive(Parser, Debug)]
struct Command {
    #[clap(subcommand)]
    subcommand: Subcommand,
}

#[derive(clap::Subcommand, Debug)]
enum Subcommand {
    Create(Create),
    Delete(Delete),
}

impl Subcommand {
    fn run(self, conn: &mut Connection) {
        match self {
            Self::Create(c) => c.run(conn),
            Self::Delete(d) => d.run(conn),
        }
    }
}

#[derive(clap::Parser, Debug)]
struct Create {
    name: String,
}

impl Create {
    fn run(self, conn: &mut Connection) {}
}

#[derive(clap::Parser, Debug)]
struct Delete {
    id: usize,
}

impl Delete {
    fn run(self, conn: &mut Connection) {}
}

fn main() {
    let command = Command::parse();
    let mut connection = Connection;

    command.subcommand.run(&mut connection);
}

Passing the resource in from main gives you more flexibility in how it's created than any of the global variable strategies do. It's slightly more verbose than a global variable would be in a language like JS, but not by a lot and you don't have to deal with Mutexes or anything like that.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.