The New MongoDB Rust Driver
Today, we are releasing version 0.1.0 of the experimental MongoDB driver for the Rust programming language. This is the spiritual successor to the driver for Rust 0.7, built two years ago by previous interns Jao-ke Chin-Lee and Jed Estep. In those two years, Rust has changed dramatically to become a language that is faster, safer, more concurrent, and more usable for developers. We’re excited to be releasing our driver into the wilderness, and look forward to feedback from both the Rust and MongoDB communities.
About Rust
Rust is a new systems-oriented programming language currently in development at Mozilla. Officially, it is described as a language that “runs blazingly fast, prevents nearly all segfaults, and guarantees thread safety.” Its powerful type- and memory-safety features run at compile time, eliminating common missteps like dangling pointers and data races at runtime while maintaining performance typical of a low-level language.
In addition, it features elements from imperative, functional, and object-oriented programming languages, providing high-level abstractions like trait-based generics, pattern matching, type inferencing, and automatic memory management at little to no runtime cost. As the language continues to grow, the availability of a MongoDB driver written in native Rust exposes both Rust and MongoDB to new audiences.
The New MongoDB Rust Driver
The driver is an open-source, Apache-licensed library implemented entirely in Rust. It will feel familiar to users of the current generation of MongoDB drivers, which have been rebuilt to conform to a growing collection of published specifications, guiding everything from the user-facing API to the internal mechanics of server monitoring and selection. The driver is available on crates.io as mongodb.
Installation
To use the driver as a library dependency, add the bson and mongodb crates to your Cargo.toml
:
[dependencies]
bson = "0.1.3"
mongodb = "0.1.0"
Afterwards, you can import the crates within your code and use them freely.
#[macro_use(bson, doc)]
extern crate bson;
extern crate mongodb;
Usage
Connecting to MongoDB To connect to a MongoDB server, we need to create a client. We can connect directly to a server on localhost:27017 like so:
let client = Client::connect("localhost", 27017)
 .ok().expect("Failed to initialize client.");
We can also connect to a more complex topology, such as a replica set or sharded cluster:
let client = Client::with_uri("mongodb://localhost:27017,localhost:27018")
 .ok().expect("Failed to initialize client.");
Connecting with a connection string will trigger automatic monitoring of your servers, allowing the driver to discover other servers within your server set and to react to changes within your deployment topology.
More complex options are available through the ClientOptions
struct, allowing you to specify log files for command monitoring, read preferences and write concerns for complex topologies, and other configuration options like server timeouts and heartbeat monitoring intervals.
Databases Now that we’ve connected to our servers, let’s create a new capped collection within the ‘movies’ database. We can easily do this through the database API:
let db = client.db("movies");
<p>let options = CreateCollectionOptions::new();
options.capped = true;
options.size = Some(100000);</p>
<p>db.create_collection("comedies", Some(options))
.ok().expect("Failed to create 'comedies' collection!");
If the database is protected using SCRAM-SHA-1 authentication measures, we can authorize ourselves through the driver before executing our operations:
db.auth("root", "very_secure_password")
 .ok().expect("Failed to authorize user 'root'.");
<p>// List collections without a filter.
let collections = db.list_collections(None)
.ok().expect(“Are you sure you’ve been authorized?”);
At this time other authentication measures are unimplemented. We look forward to supporting the full auth specification in the future.
CRUD Now we can interact with our new collection. For those that have used other drivers within the MongoDB ecosystem, interacting with your data in Rust will feel very familiar. We can do things like insert multiple documents into our collection:
let coll = db.collection("comedies");
let film_a = doc!{ "title" => "Ferris Bueller’s Day Off" };
let film_b = doc!{ "title" => "Airplane!" };
<p>// Insert multiple documents with default options.
coll.insert_many(vec![film_a, film_b], None)
.ok().expect("Failed to insert documents.");</p>
<p>// Update a single document.
let filter = film_a.clone();
let update = doc!{ "director" => "John Hughes" };</p>
<p>// Update a film with director information.
coll.update_one(filter, update, None)
.ok().expect("Failed to update document.");</p>
<p>// Remove all documents.
coll.delete_many(doc!{}, None)
.ok().expect("Failed to delete documents.");
Along with the basic CRUD operations, the driver provides a bulk write API for efficiently executing multiple write operations at once.
// Generate a list of InsertOne write models.
let models = (1..5).map(|i| WriteModel::InsertOne { document: doc! {
 "_id" => (i),
 "x" => (i * 11)
}}).collect();
<p>let new_coll = db.collection("example");</p>
<p>// Execute InsertOne operations, ordered.
new_coll.bulk_write(models, true);
Cursors
The return type of a query from the database is Result<Cursor>
. This means that if the query completed successfully, a Cursor
will be returned, and if not, an Error
will be returned. The most common way to check whether the query was successful is to pattern match on the result:
let cursor = match coll.find(doc! { age: { $gte: 18 } } , None) {
 Ok(cursor) => cursor,
 Err(error) => println!("The following error occured: {}", error)
};
The Cursor
class implements the Iterator
trait. The iteration type is Result<bson::Document>
. You can iterate over the documents by using a “for” loop:
for doc in cursor {
 println!("{}", doc.unwrap()); 
}
You can also use any of the Iterator
methods on the cursor:
// Gets a vector of all the documents returned from a query
let docs : Vec<_> = cursor.map(|doc| doc.unwrap()).collect();
Tariff - an import/export app
To test our driver, we wrote a few applications using it. One of these, tariff
, serves as a good example of some basic driver usage.
Description
tariff
is an import/export tool for MongoDB databases. It has the ability to dump either single collections or entire databases into a JSON file and to import the files generated by an export. By default, tariff
will read from whatever database it connects to, but it also has the ability to read from a secondary in a replica set rather than a primary.
Installation
To install tariff
, clone the github repository and then build the app using cargo
:
git clone https://github.com/saghm/tariff
cd tariff
cargo build --release
Usage
After following the above steps, the tariff
binary will be located in target/release/tariff
. To see a list of available arguments, you can run tariff --help
.
Examples
Note: all of the below examples assume that they’re being run from the same directory as the tariff
binary. You may need to prefix the name with the path to it; for example, to run from the project root, you’ll need to use target/release/tariff
.
- Exporting the collection “players” in the “baseball” database to the file “players.json”:
tariff -e players.json -d baseball -c players
- Exporting the entire “baseball” database to the file “baseball.json”:
tariff -x baseball.json -d baseball
- Importing the collection stored in “players.json” into the collection “baseball” in the database “athletes”:
tariff -i players.json -d athletes -c baseball
- Importing all collections stored in “baseball.json” into the database "favorite
_
sport”: tariff -m baseball.json -d favorite
_sport
- Exporting a collection from one of the secondaries in a replica set rather than the primary:
tariff -e players.json -d mlb -c players --secondary
- Importing a collection into a database running on port 3000:
tariff -i players.json -d atheletes -b baseball -p 3000
Driver Code
To see how tariff
uses the Rust driver, you can look at the file src/client.rs.
Resources
To learn more about the driver, visit the github repositories for both the bson library and the driver itself. Documentation is available on github pages. We highly encourage you to play around with the driver and to provide feedback in the form of issues and pull requests!
To learn more about Rust in general, check out our slides for the Rust NYC meetup hosted at MongoDB HQ. Below is an enumeration of online resources that we have used and recommended to become more familiar with Rust and to claim a deeper understanding of the internal workings of the language:
- The Rust Book
- Wrapper Types in Rust: Choosing Your Guarantees
- Error Handling in Rust
- Some Notes on Send and Sync
- A Practical Intro to Macros in Rust 1.0
- Rust by Example
Thank you to the Rust community for providing all of the tools and resources that make it easy to pick up and become familiar with the language, and to all of the contributors we’ve interacted with that have made our lives easier. A final thank you to all of the wonderful people at MongoDB that make the internship program special, especially our mentors, Daniel Alabi and Valeri Karpov, for their guidance and support.
Want more MongoDB? Read our white paper to learn about the latest features in MongoDB 3.0:
About the Authors - Sam & Kevin
Sam Rossi is a rising senior at the University of Pennsylvania majoring in computer science. He enjoys doing systems programming, writing compilers and interpreters, and solving interesting problems in general.
Kevin Yeh is a rising Master's student at the University of Texas at Austin, and was an intern on the Drivers team at MongoDB this summer.