Get Started with Rust and MongoDB
Rate this quickstart
This series assumes that you have a recent version of the Rust toolchain installed (v1.57+), and that you're comfortable with Rust syntax. It also assumes that you're reasonably comfortable using the command-line and your favourite code editor.
Rust is a powerful systems programming language with high performance and low memory usage which is suitable for a wide variety of tasks. Although currently a niche language for working with data, its popularity is quickly rising.
If you use Rust and want to work with MongoDB, this blog series is the place to start! I'm going to show you how to do the following:
- Connect to a MongoDB instance.
- Create, Read, Update & Delete (CRUD) documents in your database.
Later blog posts in the series will cover things like Change Streams, Transactions and the amazing Aggregation Pipeline feature which allows you to run advanced queries on your data.
I'm going to assume you have a working knowledge of Rust. I won't use any complex Rust code - this is a MongoDB tutorial, not a Rust tutorial - but you'll want to know the basics of error-handling and borrowing in Rust, at least! You may want to run
rustup updateif you haven't since January 2022 because I'll be working with a recent release.
You'll need the following:
You'll use MongoDB Atlas to host a MongoDB cluster, so you don't need to worry about how to configure MongoDB itself.
- Enter your details, or just sign up with your Google account, if you have one.
- Accept the Terms of Service
- Create a Starter cluster.
- Select the same cloud provider you're used to, or just leave it as-is. Pick a region that makes sense for you.
- You can change the name of the cluster if you like. I've called mine "RustQuickstart".
It will take a couple of minutes for your cluster to be provisioned, so while you're waiting you can move on to the next step.
In your terminal, change to the directory where you keep your coding projects and run the following command:
This will create a new directory called
rust_quickstartcontaining a new, nearly-empty project. In the directory, open
Cargo.tomland change the
[dependencies]section so it looks like this:
Now you can download and build the dependencies by running:
You should see lots of dependencies downloaded and compiled. Don't worry, most of this only happens the first time you run it! At the end, if everything went well, it should print "Hello, World!" in your console.
Your MongoDB cluster should have been set up and running for a little while now, so you can go ahead and get your database set up for the next steps.
In the Atlas web interface, you should see a green button at the bottom-left of the screen, saying "Get Started". If you click on it, it'll bring up a checklist of steps for getting your database set up. Click on each of the items in the list (including the optional "Load Sample Data" item), and it'll help you through the steps to get set up.
Following the "Get Started" steps, create a user with "Read and write access to any database". You can give it a username and password of your choice - take a note of them, you'll need them in a minute. Use the "autogenerate secure password" button to ensure you have a long random password which is also safe to paste into your connection string later.
When deploying an app with sensitive data, you should only allow the IP address of the servers which need to connect to your database. Click the 'Add IP Address' button, then click 'Add Current IP Address' and finally, click 'Confirm'. You can also set a time-limit on an access list entry, for added security. Note that sometimes your IP address may change, so if you lose the ability to connect to your MongoDB cluster during this tutorial, go back and repeat these steps.
Now you've got the point of this tutorial - connecting your Rust code to a MongoDB database! The last step of the "Get Started" checklist is "Connect to your Cluster". Select "Connect your application".
Usually, in the dialog that shows up, you'd select "Rust" in the "Driver" menu, but because the Rust driver has only just been released, it may not be in the list! You should select "Python" with a version of "3.6 or later".
Ensure Step 2 has "Connection String only" highlighted, and press the "Copy" button to copy the URL to your pasteboard (just storing it temporarily in a text file is fine). Paste it to the same place you stored your username and password. Note that the URL has
<password>as a placeholder for your password. You should paste your password in here, replacing the whole placeholder including the '<' and '>' characters.
Back in your Rust project, open
main.rsand replace the contents with the following:
In order to run this, you'll need to set the MONGODB_URI environment variable to the connection string you obtained above. Run one of the following in your terminal window, depending on your platform:
Once you've done that, you can
cargo runthis code, and the result should look like this:
Congratulations! You just connected your Rust program to MongoDB and listed the databases in your cluster. If you don't see this list then you may not have successfully loaded sample data into your cluster - you'll want to go back a couple of steps until running this command shows the list above.
Before you go ahead querying & updating your database, it's useful to have an overview of BSON and how it relates to MongoDB. BSON is the binary data format used by MongoDB to store all your data. BSON is also the format used by the MongoDB query language and aggregation pipelines (I'll get to these later).
It's analogous to JSON and handles all the same core types, such as numbers, strings, arrays, and objects (which are called Documents in BSON), but BSON supports more types than JSON. This includes things like dates & decimals, and it has a special ObjectId type usually used for identifying documents in a MongoDB collection. Because BSON is a binary format it's not human readable - usually when it's printed to the screen it'll be printed to look like JSON.
Because of the mismatch between BSON's dynamic schema and Rust's static type system, dealing with BSON in Rust can be tricky. Fortunately the
bsoncrate provides some useful tools for dealing with BSON data, including the
doc!macro for generating BSON documents, and it implements
for the ability to serialize and deserialize between Rust structs and BSON data.
Creating a document structure using the
doc!macro looks like this:
If you use
println!to print the value of
new_docto the console, you should see something like this:
(Incidentally, Parasite is an absolutely amazing movie. It isn't already in the database you'll be working with because it was released in 2020 but the dataset was last updated in 2015.)
Although the above output looks a bit like JSON, this is just the way the BSON library implements the
Displaytrait. The data is still handled as binary data under the hood.
If you've browsed the movies collection with
or the "Collections" tab in Atlas, you'll see that most of the records have more fields than the document I built above using the
doc!macro. Because MongoDB doesn't enforce a schema within a collection by default, this is perfectly fine, and I've just cut down the number of fields for readability. Once you have a reference to your MongoDB collection, you can use the
insert_onemethod to insert a single document:
insert_onemethod returns the type
Result<InsertOneResult>which can be used to identify any problems inserting the document, and can be used to find the id generated for the new document in MongoDB. If you add this code to your main function, when you run it, you should see something like the following:
This code inserts a single
Documentinto a collection. If you want to insert multiple Documents in bulk then it's more efficient to use
insert_manywhich takes an
IntoIteratorof Documents which will be inserted into the collection.
Because I know there are no other documents in the collection with the name Parasite, you can look it up by title using the following code, instead of the ID you retrieved when you inserted the record:
This code should result in output like the following:
It's very similar to the output above, but when you inserted the record, the MongoDB driver generated a unique ObjectId for you to identify this document. Every document in a MongoDB collection has a unique
_idvalue. You can provide a value yourself if you have a value that is guaranteed to be unique, or MongoDB will generate one for you, as it did in this case. It's usually good practice to explicitly set a value yourself.
method is useful to retrieve a single document from a collection, but often you will need to search for multiple records. In this case, you'll need the
method, which takes similar options as this call, but returns a
Cursoris used to iterate through the list of returned data.
The find operations, along with their accompanying filter documents are very powerful, and you'll probably use them a lot. If you need more flexibility than
find_onecan provide, then I recommend you check out the documentation on
which are super-powerful and, in my opinion, one of MongoDB's most powerful features. I'll write another blog post in this series just on that topic - I'm looking forward to it!
Once a document is stored in a collection, it can be updated in various ways. If you would like to completely replace a document with another document, you can use the
method, but it's more common to update one or more parts of a document, using
. Each separate document update is atomic, which can be a useful feature to keep your data consistent within a document. Bear in mind though that
update_manyis not itself an atomic operation - for that you'll need to use
, available in MongoDB since version 4.0 (and available for sharded collections since 4.2). Version 2.x of the Rust driver supports transactions for replica sets.
To update a single document in MongoDB, you need two BSON Documents: The first describes the
to find the document you'd like to update; The second Document describes the
you'd like to conduct on the document in the collection. Although the "release" date for Parasite was in 2020, I think this refers to the release in the USA. The correct year of release was 2019, so here's the code to update the record accordingly:
When you run the above, it should print out "Updated 1 document". If it doesn't then something has happened to the movie document you inserted earlier. Maybe you've deleted it? Just to check that the update has updated the year value correctly, here's a
find_onecommand you can add to your program to see what the updated document looks like:
When I ran these blocks of code, the result looked like the text below. See how it shows that the year is now 2019 instead of 2020.
In the above sections you learned how to create, read and update documents in the collection. If you've run your program a few times, you've probably built up quite a few documents for the movie Parasite! It's now a good time to clear that up using the
delete_manymethod. The MongoDB rust driver provides 3 methods for deleting documents:
find_one_and_deletewill delete a single document from a collection and return the document that was deleted, if it existed.
delete_onewill find the documents matching a provided filter and will delete the first one found (if any).
delete_many, as you might expect, will find the documents matching a provided filter, and will delete all of them.
In the code below, I've used
delete_manybecause you may have created several records when testing the code above. The filter just searches for the movie by name, which will match and delete all the inserted documents, whereas if you searched by an
_idvalue it would delete just one, because ids are unique.
You did it! Create, read, update and delete operations are the core operations you'll use again and again for accessing and managing the data in your MongoDB cluster. After the taster that this tutorial provides, it's definitely worth reading up in more detail on the following:
One of the features of the bson crate which may not be readily apparent is that it provides a BSON data format for the
serdeframework. This means you can take advantage of the serde crate to map between Rust datatypes and BSON types for persistence in MongoDB.
For an example of how this is useful, see the following example of how to access the
titlefield of the
new_moviedocument (without serde):
The first line of the code above retrieves the value of
titleand then attempts to retrieve it as a string (
Noneif the value is a different type). There's quite a lot of error-handling and conversion involved. The serde framework provides the ability to define a struct like the one below, with fields that match the document you're expecting to receive.
Note the use of the
Deserializemacros which tell serde that this struct can be serialized and deserialized. The
serdeattribute is also used to tell serde that the
idstruct field should be serialized to BSON as
_id, which is what MongoDB expects it to be called. The parameter
skip_serializing_if = "Option::is_none"also tells serde that if the optional value of
Nonethen it should not be serialized at all. (If you provide
_id: NoneBSON to MongoDB it will store the document with an id of
NULL, whereas if you do not provide one, then an id will be generated for you, which is usually the behaviour you want.) Also, we need to use an attribute to point
serdeto the helper that it needs to serialize and deserialize timestamps as defined by
The code below creates an instance of the
Moviestruct for the Captain Marvel movie. (Wasn't that a great movie? I loved that movie!) After creating the struct, before you can save it to your collection, it needs to be converted to a BSON document. This is done in two steps: First it is converted to a Bson value with
bson::to_bson, which returns a
Bsoninstance; then it's converted specifically to a
as_documenton it. It is safe to call
unwrapon this result because I already know that serializing a struct to BSON creates a BSON document type.
When I ran the code above, the output looked like this:
It's great to be able to create data using Rust's native datatypes, but I think it's even more valuable to be able to deserialize data into structs. This is what I'll show you next. In many ways, this is the same process as above, but in reverse.
The code below retrieves a single movie document, converts it into a
Bson::Documentvalue, and then calls
from_bsonon it, which will deserialize it from BSON into whatever type is on the left-hand side of the expression. This is why I've had to specify that
loaded_movieis of type
Movieon the left-hand side, rather than just allowing the rust compiler to derive that information for me. An alternative is to use the
notation on the
from_bsoncall, explicitly calling
from_bson::<Movie>(loaded_movie). At the end of the day, as in many things Rust, it's your choice.
And finally, here's what I got when I printed out the debug representation of the Movie struct (this is why I derived
Debugon the struct definition above):
If you prefer to use
tokio, you're in luck! The changes are trivial. First, you'll need to disable the defaults features and enable the
The only changes you'll need to make to your rust code is to add
use async_std;to the imports and tag your async main function with
#[async_std::main]. All the rest of your code should be identical to the Tokio example.
If you don't want to run under an async framework, you can enable the sync feature. In your
Cargo.tomlfile, disable the default features and enable
You won't need your enclosing function to be an
async fnany more. You'll need to use a different
Clientinterface, defined in
mongodb::syncinstead, and you don't need to await the result of any of the IO functions:
The documentation for the MongoDB Rust Driver is very good. Because the BSON crate is also leveraged quite heavily, it's worth having the docs for that on-hand too. I made lots of use of them writing this quick start.
Phew! That was a pretty big tutorial, wasn't it? The operations described here will be ones you use again and again, so it's good to get comfortable with them.
What I learned writing the code for this tutorial is how much value the
bsoncrate provides to you and the mongodb driver - it's worth getting to know that at least as well as the
mongodbcrate, as you'll be using it for data generation and conversion a lot and it's a deceptively rich library.