Docs Menu
Docs Home
/ / /
Rust Driver
/ / /

Access Data by Using a Cursor

On this page

  • Overview
  • Sample Data for Examples
  • Retrieve Documents Individually
  • Built-in Pattern
  • Stream Implementation Pattern
  • Retrieve Documents as an Array
  • Specify Cursor Behavior
  • Additional Information
  • API Documentation

In this guide, you can learn how to use the Rust driver to access data returned from a read operation or aggregation by using a cursor. A cursor is a mechanism that enables you to iterate through multiple documents while holding only a subset of them in memory at a given time.

The driver offers the Cursor type to retrieve documents from a cursor. For example, when you run a find operation that can return multiple documents, the driver returns a Cursor instance from which you can access the matched documents.

After you run a read operation or aggregation, the returned Cursor instance contains the first batch of results from the operation. As you iterate through the cursor, the server returns more individual results. If there are more matching documents after you reach the end of a batch of results, the Cursor instance fetches the next batch of documents until all the results are returned.

This guide includes the following sections:

  • Sample Data for Examples presents the sample data that is used by the cursor examples

  • Retrieve Documents Individually describes how to use iteration or a stream to access results one at a time

  • Retrieve Documents as an Array describes how to access all results as a single array by collecting the returned cursor results

  • Specify Cursor Behavior describes how to configure the cursor that a method returns

  • Additional Information provides links to resources and API documentation for types and methods mentioned in this guide

The examples in this guide use the following data stored in a struct:

let docs = vec! [
Fruit {
name: "strawberry".to_string(),
color: "red".to_string()
},
Fruit {
name: "banana".to_string(),
color: "yellow".to_string()
},
Fruit {
name: "pomegranate".to_string(),
color: "red".to_string()
},
Fruit {
name: "pineapple".to_string(),
color: "yellow".to_string()
}
];

The driver provides the following access patterns to iterate through documents returned by a Cursor instance:

The following sections describe these access patterns and corresponding methods in more detail.

You can use the driver's built-in access pattern to retrieve and process documents one by one.

The Cursor type includes the advance() and deserialize_current() methods to iterate through a cursor and access documents individually.

The advance() method moves the cursor forward and sends a request to the database for more results when the local buffer is exhausted, which occurs when the cursor reaches the end of a batch of results. Each time the cursor reaches the end of a batch of results, it requests the next batch. The cursor is exhausted when it has no more matching documents to return and is no longer usable. The advance() method returns a true result if new results are successfully returned and a false result if the cursor is closed.

The deserialize_current() method returns a reference to the current result in the cursor and deserializes the result into the type associated with the cursor. Unless you specify a type, the method uses the same type that your collection is parameterized with.

Important

You can call the deserialize_current() method only if the advance() method returns a true result. The driver generates an error if you call deserialize_current() on the cursor without a true result or without calling previously calling advance().

The following example shows how to implement this access pattern to iterate through the results of a find operation on the fruits collection:

let mut cursor = my_coll.find(doc! { "color": "red" }).await?;
while cursor.advance().await? {
println!("{:?}", cursor.deserialize_current()?);
}
Fruit { name: "strawberry", color: "red" }
Fruit { name: "pomegranate", color: "red" }

You can access cursor results as a stream to retrieve individual documents or collect multiple documents at once.

The Cursor type implements the Stream trait, so you can iterate through a cursor as a stream. You can use this pattern to help you write more concise code than with the built-in pattern, because the Stream extension trait StreamExt provides numerous functions to combine operations and consolidate code.

You can use the following methods to use the stream pattern:

  • next(): advances the cursor to the next result and returns an Option<Result<T>> type

  • try_next(): advances the cursor to the next result and returns a Result<Option<T>> type

Important

Required Imports for Stream Pattern Methods

To use the next() method, you must import the StreamExt trait. To use the try_next() method, you must import the TryStreamExt trait.

The following example shows how to implement the two stream methods to iterate through the results of find operations on the fruits collection:

let mut cursor = my_coll.find(doc! { "color": "red" }).await?;
println!("Output from next() iteration:");
while let Some(doc) = cursor.next().await {
println!("{:?}", doc?);
}
println!();
let mut cursor = my_coll.find(doc! { "color": "yellow" }).await?;
println!("Output from try_next() iteration:");
while let Some(doc) = cursor.try_next().await? {
println!("{:?}", doc);
}
Output from next() iteration:
Fruit { name: "strawberry", color: "red" }
Fruit { name: "pomegranate", color: "red" }
Output from try_next() iteration:
Fruit { name: "banana", color: "yellow" }
Fruit { name: "pineapple", color: "yellow" }

Because the Cursor type implements the Stream trait, you can collect the results from a cursor into an array.

You can use the following methods to retrieve documents as an array:

  • collect(): collects results from a cursor into a Vec<Result<T>> type

  • try_collect(): collects results from a cursor into a Result<Vec<T>> type

Note

To use the collect() method, you must import the StreamExt trait. To use the try_collect() method, you must import the TryStreamExt trait.

let cursor = my_coll.find(doc! { "color": "red" }).await?;
println!("Output from collect():");
let v: Vec<Result<Fruit>> = cursor.collect().await;
println!("{:?}", v);
println!();
let cursor = my_coll.find(doc! { "color": "yellow" }).await?;
println!("Output from try_collect():");
let v: Vec<Fruit> = cursor.try_collect().await?;
println!("{:?}", v);
Output from collect():
[Ok(Fruit { name: "strawberry", color: "red" }), Ok(Fruit { name: "pomegranate", color: "red" })]
Output from try_collect():
[Fruit { name: "banana", color: "yellow" }, Fruit { name: "pineapple", color: "yellow" }]

Warning

Avoid Exceeding Application Memory Limits

Avoid converting large sets of results to arrays. If the array exceeds the size of available application memory, your application might crash. If you expect a large result set, retrieve documents from the cursor individually. To learn how to iterate through the cursor, see the Retrieve Documents Individually section of this guide.

To modify the cursor that an operation returns, chain option builder methods to the method that returns the Cursor instance. For example, you can chain cursor-related option builder methods to the find() method.

Note

Setting Options

You can set FindOptions fields by chaining option builder methods directly to the find() method call. If you're using an earlier version of the driver, you must construct a FindOptions instance by chaining option builder methods to the builder() method. Then, pass your FindOptions instance as a parameter to find().

The following table describes cursor-related options that you can set by calling their corresponding builder method:

Setting
Description
batch_size
Specifies the maximum number of documents the server returns per cursor batch. This option sets the number of documents the cursor keeps in memory rather than the number of documents the cursor returns.

Type: u32
Default: 101 documents initially, 16 MB maximum for subsequent batches
cursor_type
Specifies the type of cursor to return. You can set this option to produce a tailable cursor. To learn more about tailable cursors, see Tailable Cursors in the Server manual.

Type: CursorType
Default: CursorType::NonTailable
no_cursor_timeout
Specifies whether the server closes the cursor after a period of inactivity.

IMPORTANT: Because the Cursor type implements the Drop trait, the server closes a cursor when it goes out of scope. The server runs an asynchronous killCursors command to close the cursor. See killCursors in the Server manual to learn more.
Type: bool
Default: false

The following code shows how to specify cursor-related settings by chaining option builder methods to the find() method:

let mut cursor = my_coll.find(doc! { "color": "red" })
.batch_size(5)
.cursor_type(CursorType::Tailable)
.no_cursor_timeout(true)
.await?;

To learn more about the operations in this guide, see the following documentation:

To learn more about converting between Rust types and BSON, see the guide on Data Modeling and Serialization.

To learn more about the methods and types mentioned in this guide, see the following API documentation:

Back

Specify a Query