Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
178: MongoDB Driver Usage in Rust
I once worked with a developer who had just migrated a Node.js backend to Rust. They were flying through the logic, but they hit a wall the moment they touched MongoDB. They spent an entire afternoon fighting the compiler because they were trying to treat MongoDB's ObjectId as a simple String. In JavaScript, the driver often masks these distinctions, but Rust demands you be explicit about whether you're dealing with a BSON document, a raw string, or a typed struct. It's a common point of frustration, but once you understand how the mongodb crate leverages serde, it actually becomes a superpower for data integrity.
Taming the BSON-Serde Bridge
The key to working with MongoDB in Rust is understanding that the driver doesn't just "send data"βit translates between Rust types and BSON (Binary JSON). To make this painless, you'll almost always use serde. Instead of manually building BSON documents for everything, you define your data models as structs and derive Serialize and Deserialize.
Here is where the ObjectId trip-up happens. If you want your Rust struct to map directly to the _id field in Mongo, you can't just use a String. You should use the oid type from the bson crate. I usually recommend using the #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] attribute on an Option<ObjectId> field. This allows MongoDB to generate the ID for you on insertion while still letting you access it when you read the document back.
use serde::{Serialize, Deserialize};
use mongodb::bson::oid::ObjectId;
#[derive(Debug, Serialize, Deserialize)]
pub struct DeviceLog {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub device_id: String,
pub temperature: f64,
pub timestamp: mongodb::bson::DateTime,
}
Executing Async Queries with the doc! Macro
You'll notice the mongodb crate is heavily async, typically relying on tokio. When you're querying, you aren't writing SQL; you're building BSON documents. The driver provides a doc! macro that makes this look and feel like JSON, which saves you from the verbosity of manually constructing BSON maps.
When I'm writing query logic, I prefer to keep my collection handles typed. Instead of using a generic Collection<Document>, use Collection<DeviceLog>. This forces the driver to handle the deserialization for you, so you get a stream of your actual structs rather than a bunch of loosely typed BSON maps that you have to manually cast.
use mongodb::{Client, Collection};
use mongodb::bson::doc;
use futures::stream::TryStreamExt;
async fn get_high_temp_logs(coll: &Collection<DeviceLog>, threshold: f64) -> Vec<DeviceLog> {
let filter = doc! { "temperature": { "$gt": threshold } };
let mut cursor = coll.find(filter, None).await.expect("Query failed");
let mut results = Vec::new();
while let Some(log) = cursor.try_next().await.unwrap() {
results.push(log);
}
results
}
Managing the MongoClient Lifecycle
One thing that catches people off guard is the Client. You might be tempted to create a new client every time you need to perform a database operation, but don't do that. The Client in the Rust driver is internally wrapped in an Arc, meaning it's designed to be cloned and shared across your entire application.
I always suggest initializing the Client once at the start of your main function and passing clones of it into your request handlers or services. This ensures you're utilizing connection pooling effectively. If you create a new client for every request, you'll quickly exhaust your database's connection limit and see your latency spike as the driver spends more time performing TCP handshakes than actually moving data.
π Practical Task
Implementing a Device Heartbeat Log Store
Build a small module that manages "heartbeat" logs for a fleet of IoT devices. Your implementation must satisfy the following requirements:
- Define a
Heartbeatstruct that includes an_id(as an optionalObjectId), adevice_id(String), and astatus(String). Useserdeto ensure the_idmaps correctly to MongoDB. - Write an async function
record_heartbeatthat takes aCollection<Heartbeat>and aHeartbeatinstance, inserting the document into the database. - Write an async function
find_device_statusthat takes aCollection<Heartbeat>and adevice_id, returning the single most recentHeartbeatdocument for that device (Hint: use a sort on the_idfield in descending order, asObjectIds contain a timestamp). - Ensure your functions handle
Resulttypes properly rather than usingunwrap()inside the logic.
There are no comments for now.