Why Rust Is Not Suitable for Developing Web APIs

Why Rust Is Not Suitable for Developing Web APIs
Author | Tom MacWright
Translator | Wu Liupo
Editor | Cai Fangfang

Rust is a fascinating programming language with excellent CLI tools like ripgrep and exa. Companies like Cloudflare are using and encouraging people to write Rust to run microservices. Software written in Rust can be safer, smaller, and cleaner than C++ or C.

If I were writing a geocoder, a routing engine, a real-time messaging platform, a database, or a CLI tool, Rust would be the best choice.

However, last year, when I tried to write a pure API service for a traditional website using Rust, it turned out to be unsuitable.

Missing Many Minor Features

Rust has a large number of web service frameworks, database connectors, and parsers. However, when it comes to building authentication services, it only has very low-level components. Node.js has passport.js, Rails has devise, and Django has out-of-the-box authentication models; in Rust, you need to learn how to convert a shared Vec into the underlying cryptography library to build this system.

Translator’s note: Vec is a dynamic array that only grows automatically and does not shrink. Unlike an Array, Vec has the ability to dynamically add and remove elements and can access elements randomly with O(1) efficiency. All items in a Vec are generated in heap space, making it easy to move a Vec out of a stack without worrying about memory copy affecting execution efficiency, as it only copies the pointer on the stack.

Some libraries try to address this issue, such as libreauth, but it is still in early development. There are many similar issues with web frameworks.

What about SDKs? In mainstream programming languages, you can access Google Cloud, AWS, or Stripe through an official library. Most of these official libraries are excellent. For example, aws-sdk-js and the Stripe library are well-designed and maintained.

Rust does not have this; there are only a few third-party libraries, and given the development speed of these services, can they really provide a high-quality experience?

Some may say, “Well, X programming language is so great; you can write an SDK yourself over the weekend!” I have to respond, “No.”

The ecosystem of Rust is very rich in other areas. The crates for building CLIs, managing concurrency, using binary data, and low-level parsers are impressive and fantastic.

The Rust Compiler Is Faster Than Before, But Still Slow

I have been following Nicholas Nethercote’s blog, which describes how the Rust team optimizes the compiler to make it faster!

However, compared to other programming languages, building websites with it is slow. It is much slower than the compiled programming language Go and also much slower than interpreted programming languages like JavaScript, Ruby, and Python.

Once the code is compiled, everything is fantastic! But in my case, even basic API functionality was incomplete, and a not-so-complex system took over 10 minutes to compile. The hardware configuration for Google code builds is poor, and it times out every time, leaving me unable to compile anything.

As long as you do not rebuild cached dependencies, caching makes sense. Perhaps reducing dependencies can speed up Rust project compilation. But like serde, which is used by almost everyone for JSON and other serialization/deserialization, it occupies a large amount of compilation time. Should we replace serde with something that compiles faster but lacks extensive documentation and ecosystem support? This trade-off is very poor.

Rust Is Complex

Rust makes you think from a code dimension, which is very important for system programming. It forces you to think about how to share or copy memory, consider real but unlikely low-probability events, and ensure they are handled properly, helping you write various efficient codes.

These concerns are reasonable, but for most web applications, they are not the most important focus. Thinking with popular inertia can lead to incorrect assumptions.

Take the safety of Rust as an example. This is an important part of its slogan, and it is absolutely correct: Rust promises both safety and low-level performance—it can work without a garbage collector while preventing memory-based vulnerabilities. When you read “safety,” think of C, its competitor. Code in C can reference arbitrary memory, leading to easy overflows and errors. Rust code can be as fast as C code, but it protects memory access without requiring a garbage collector or some kind of runtime checks.

However, the memory rules of Rust are not safer than Node.js or Python. Web applications written in Rust are not safer on the system than those written in Python or Ruby. High-level programming languages with garbage collectors usually pay a performance penalty to avoid such vulnerabilities and errors. You cannot reference uninitialized memory in JavaScript because JavaScript does not allow memory referencing between memory spaces.

Note: This describes the design goals of Node.js and other systems—they do occasionally have bugs. The cache objects in Node.js are worth reading about.

If you ask some people, they will say that if you use unsafe code, Rust is less safe than programming languages with memory reclamation—including the popular web framework Actix (Translator’s note: Actix is an asynchronous actor framework for Rust based on Tokio and Future, which provides asynchronous non-blocking event-driven concurrency capabilities using a low-level actor model to provide a lock-free concurrency model while also providing synchronous actors, being fast, reliable, and easily scalable https://actix.rs/), because unsafe code allows for raw pointer delays.

If you are writing a video game, pausing to execute garbage collection is bad. If you are writing microcontroller code, any memory “overhead” or waste is very bad. However, most web applications can afford to save a bit of memory overhead for production performance.

The other properties of Rust face similar controversies. Its concurrency features are amazing; if you are doing something complex and need quick responses, this is certainly great. But what if that is not the case? At least it can be said that Rust‘s asynchronous ecosystem faces significant challenges: there are different asynchronous implementations in various unrelated domains, such as tokio.

In contrast, Python‘s Tornado and Twisted asynchronous implementations are quite strange, while Node.js‘s asynchronous implementation works well, but the syntax is quite ugly.

I am confident that Rust‘s asynchronous capabilities will stabilize and unify, making it easier to operate in the future, but I need to use it now.

The Rust Ecosystem Is Not Web-Centric

Many people are learning Rust, writing CLI applications or low-level code, and having a great time. However, significantly fewer people are using Rust to write ordinary web applications.

This is an important part of technical choice: Is anyone using the tool? Are they roughly in the same field? Unfortunately, much of the incredibly exciting work in the Rust ecosystem is unrelated to web application servers. There are indeed some promising web frameworks—even higher-level frameworks—but there is no doubt that their market is small. Even the main web framework Actix has only a few top contributors.

If Rust continues to grow at its current rate, the web portion of the community will reach a critical mass, but I believe there are not enough people using Rust as a practical tool for websites. Compared to other communities, many companies are committed to using existing tools to build web applications that are not cutting-edge but are sufficient to distinguish mature technology from new technology.

Juniper’s N+1 Query

This section involves not just Rust, but also the GraphQL ecosystem, and Rust‘s participation in this ecosystem is an example.

The N+1 problem is something every web application builder should know. The point is: you have a page of photos (one query), and you want to display the author of each photo. How many queries will there be: 1, merging photos and authors, or querying each photo for the author after retrieving the photos? Or two, the second query fetching all authors by querying the ids in user.id, and then resetting their photo properties.

N+1 queries are usually prioritized using databases: for example, converting N+1 queries into a single query brings significant performance optimization. We have many ways to try and solve these problems: you can write SQL and attempt to do a lot of work in a single query using CTE and JOIN, as we do in Observable, or use an ORM layer like ActiveRecord to quickly convert N+1 queries into predictable queries.

Juniper is a GraphQL service for Rust applications. GraphQL is essentially defined by front-end applications that specify queries rather than the backend. Give it a series of things to query, and then the application (like React or others) will send arbitrary queries to the backend.

This complicates the backend. Any SQL-level optimization is impossible—your server is writing dynamic SQL, and optimizations can only rely on the GraphQL service, which will not always be effective. For example, Juniper performs N+1 queries by default, and the solution dataloader is still rough and requires separate maintenance. Therefore, in the end, you will have a very fast application layer, but all its time is spent on extremely inefficient database queries.

In summary, GraphQL works very well with NoSQL databases, providing quick service for these types of requests. I am confident that Facebook has some specific databases that work excellently with GraphQL, but other companies in the industry heavily rely on Postgres and similar products.

Some Considerations

First, the issues mentioned in this article do not target the general use of Rust, but specifically using Rust for web APIs.

Consideration 1: Generally, you can build a website with any programming language. Remember OkCupid, which is implemented in C++? (Translator’s note: OkCupid is a large online dating site in the U.S.) There is also a very popular astrology application, Co-star, which is entirely written in Haskell. If you are proficient in other programming languages or can hire engineers skilled in those languages, you can still succeed.

Consideration 2: What I attempted to build was a heavy CRUD (Create, Read, Update, Delete) web application API. It may not be a web “service”—mainly executing the same operation quickly and repeatedly—but rather a web “application”—executing many different operations and containing quite a bit of business logic. If what you are developing is different from what I am doing, my advice may not suit you. If you need to quickly execute one or two operations, such as writing a payment gateway or a voice messaging application, then Rust might perform quite well.

Consideration 3: This article was written in January 2021. If the community continues to develop, Rust will see ongoing improvements, becoming better and easier for web application development.

In summary, I truly enjoy using Rust; it is a beautiful programming language with many cool ideas. I hope that soon, Rust will become the most suitable tool for building what I want to create. However, many things I want to do now require using different programming languages with distinct features to run better.

Further Reading

https://macwright.com/2021/01/15/rust.html

Event Recommendation

Initially, I followed Tao Hui’s blog, then read his book, and later his column. As a former Alibaba Cloud P8, he is indeed a big shot. Recently, he started a training class, a 2-day live course + intensive training, and you can master network protocols in just one weekend.

Now using the discount code “taohui666”, it costs ¥449, and the price will increase to ¥599 on February 1st.

There are still 300 spots left, and if you’re not satisfied, you can get a full refund at any time. I recommend signing up if you need it.

Why Rust Is Not Suitable for Developing Web APIs

Leave a Comment