Commonly Used Rust Backend Development Libraries

Today, I will introduce some libraries that I commonly use during backend development with Rust.

Basic Frameworks

First, let’s discuss the basic development frameworks for backend.

In Rust, the optional web frameworks include: Axum, Actix-Web, Warp, and Rocket.

There are also some others not listed here due to their lower popularity compared to the above.

Among them, Axum and Rocket may be easier to get started with. I currently use Rocket more frequently, so the subsequent backend development will primarily revolve around Rocket.

Rocket is a very easy-to-use web framework with the following features:

  • • Simple to UseRocket is very straightforward to use. For example, you can start an HTTP service with the following lines of code:
#[macro_use] extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![index])
}
  • • Extensible FairingFairing is a component similar to a Filter that can execute response logic at different stages. We can use Fairing to implement common functionalities such as permission verification and parameter extraction.
  • • Serialization and Deserialization SupportRocket uses serde by default for serialization and deserialization, with JSON serialization handled by serde_json.
  • • Custom Response TypesBy implementing a Responder trait, you can customize different response data, and it also has built-in support for Json responses.
  • • SSE SupportIt supports streaming responses, which is very useful for server-side push and certain AI scenarios (like streaming replies).
  • • High PerformanceIn my WSL environment, stress testing can achieve over 100,000 QPS, with stable memory usage.

ORM Frameworks

Basically, mainstream databases are supported in Rust, each having a Rust client. However, we focus more on ORM frameworks. In Java, the most commonly used is MyBatis, while in Rust, there is a similar one called Rbatis.

Rbatis is an ORM framework maintained by domestic developers, supporting dynamic SQL. It allows writing complex SQL using HTML, which is parsed into Rust code at compile time, with no runtime overhead. It supports common databases like Sqlite, MySQL, and PostgreSQL, and it is the ORM framework I commonly use.

Another popular option is Diesel, which is comprehensive in functionality and performs well. It supports joins and type safety, but it does not support dynamic SQL very well, making complex queries somewhat cumbersome and with a relatively high learning curve.

Then there is sqlx, which is not exactly an ORM framework but is simple and flexible, suitable for small projects.

Caching

Caching is mainly divided into three types:

  • • In-Memory Cache
  • • Local Cache
  • • Distributed Cache

A good in-memory cache option is moka, which is comparable to Caffeine in Java. It supports setting expiration times for individual cache items, cache capacity settings, and different eviction strategies.

Local cache actually refers to in-memory cache + persistence. Moka itself does not support persistence, so the cached content will be lost after the process restarts. Therefore, we need to implement a persistence strategy ourselves. For example, we can integrate sled to periodically persist data to disk, either fully or incrementally, and load it back into moka upon restart.

For distributed caching, Redis is commonly used. In Rust, there is a very handy Redis client called deadpool-redis, which, as the name suggests, uses deadpool for Redis connection pooling and is also very easy to use.

Serialization and Deserialization

In web development, serialization and deserialization usually refer to JSON. In Java, there are FastJson and Jackson, while in Rust, we have serde_json.

serde_json is based on serde for JSON processing, and serde itself is a general serialization and deserialization framework in Rust, providing a unified trait and some macros.

serde_json has become the de facto standard for JSON serialization and deserialization in Rust. It is very simple to use; just add<span>#[derive(Serialize, Deserialize)]</span>to a struct to support serialization and deserialization. For example:

#[derive(Debug, Serialize, Deserialize)]
pub struct RouteListRes {
    #[serde(flatten)]
    pub inner: Route,
}

Besides JSON, there is also protobuf, which Rust supports through prost, allowing for easy encoding and decoding. It also supports generating rs files from proto files, with the build tool called prost-build, which we can configure in build.rs to specify what needs to be generated.

There is also bincode, which can be used for high-performance serialization and deserialization of non-JSON data (though it can serialize JSON as well). Bincode can directly serialize objects into binary format, compact and efficient, suitable for scenarios with limited storage and high performance needs.

Message Queues

Common message queues include Kafka, RabbitMQ, RocketMQ, and other established options. These message queues, as middleware, require server-side support. They all have Rust client implementations, and their usage is generally consistent with other languages, so most of the time, you only need to focus on the client API.

Another one worth recommending is NATS, which is also a high-performance messaging middleware, supporting most platforms and easy to use. NATS itself does not provide persistence; if persistence is needed, you must use its persistence engine, JetStream. NATS supports different QoS levels, allowing you to choose the appropriate one for different scenarios to balance performance and accuracy requirements.

Logging

The Rust log crate defines the basic logging interface, and many different logging libraries implement these interfaces, such as tracing-log and env_logger.

env_logger is very lightweight and suitable for logging output in small projects. It currently cannot directly output to files but can be implemented through a custom Writer. It supports parsing log configurations from environment variables (like RUST_LOG) and filtering by module and log level.

tracing-log itself only provides an adapter for log; if you want to use it as a logging library, you also need the tracing and tracing-subscriber libraries, which are used to collect and filter logs. tracing-appender can generate rolling log files by minute, hour, day, etc., making it suitable for large projects.

The functionality of tracing goes beyond just logging; it can also be used for distributed tracing, making it a comprehensive library.

Sometimes, logging to a file alone is not enough; we want to centralize it for unified processing and analysis. You can use tracing-subscriber to implement a custom Writer to send logs to a remote endpoint. If you only need to store and query logs, you might consider quickwit, which is a high-performance log storage and querying system that can easily handle large volumes of logs and return responses in milliseconds (in tests, querying millions of logs returned responses within 10 milliseconds).

Other Common Libraries

rand: Random number generation librarybase64: Base64 encoding and decodingchrono: Date and time library, which is very commonly used and provides a wealth of date and time manipulation APIsstrum: Enumeration traversal and mapping of enumeration valuesreqwest: For simple HTTP requeststokio-cron-scheduler: Scheduled tasksdashmap: Simplifies issues brought by hashmap in concurrent programming

Leave a Comment