Rust for the Web
RESTful API in Rust, impressions
Almost 3 months ago my employer accepted my idea to write our API in Rust
First “dev” version of new API was deployed after 20 days — I was not only writing new API, but also learning new programming language. First steps were slow, initial learning curve is steep enough. A lot, a lot of times I was near to change my decision and ask my employer to switch back to PHP, but each time Rust community was kind, friendly and smart enough to give me advice how to solve my issue. I’m endlessly thankful to them.
Right now 2 of our web apps (Angular 2 SPA) are published and working in “production” mode, using this REST API. These apps are very important for business they serve.
This thing was not really difficult to understand. Of course, few first weeks I experienced a lot of errors from compiler about borrowing and lifetimes, but it’s because in other languages we don’t have such strict checks, and now I’m sure it’s a “must have” thing.
Here is how I understand Rust ownership system in my brains: you can pass variable to some context either by value or by reference. In first case be ready to say “good bye, I will never use you in this context”. Really, you gave whole value — nothing left. Make a copy (clone) if you need same value in following lines. In second case (reference) just be sure that source context will live longer than target context. Otherwise, when source context will try to die, we will have to kill value with context it belongs to, and target context will use reference to dead value — compiler will not allow us to be so cruel. Same rules for returning values from function — it’s also sending them from one context to another.
There are some more details, but these 2 rules helped me to almost forget about compiler errors, related to ownership/borrowing/lifetimes.
This ownership system removes a lot, a lot of bugs, especially dangerous things like race conditions, when code in other language will happily write and damage your data and you will not see any warnings or failures, sometimes you will not even notice bug itself (because race conditions can be very tricky to catch). It’s very important for asynchronous algorithms also, when multiple execution flows can mutate one resource (variable, object, collection). Rust takes care of it, and gives you handy tools to work with it.
Compiler is smart and gives very descriptive error messages, instead of just “no, I won’t compile it”. Sometimes you’ll even find generated examples of code to fix your errors!
Now I feel compiler is my friend. Emotions of first week were “stop being so pedantic, I know better it will work”. One month later I was running cargo build with words “ok, now show me where I’m wrong”. Then was period of “damned strings”. Now I rarely see errors and almost all of them are related to things I forgot to change during refactoring — because I can refactor code without fear and I know compiler will show me where I forgot something. And I use this feature.
Most bright star here is Cargo. Cargo is awesome, I love it so much! It can help you compile your code, run tests, generate documentation, bring you crates (libraries), you will not worry about different crates versions on different machines, using same repository. I wish we had same tool in other languages also.
Almost all things I tried for my web-dev needs, are covered by crates. Some of them are not as mature as I wish, but most of them are pretty usable, well documented, and with strict compiler you can be sure — if it compiled, it will work. Frameworks Iron and Nickel are pretty active in development and mature enough.
I’m glad to see when crates have no “unsafe” code and when they are trying to avoid usage of “panic”. I think we need some kind of badges to promote such crates.
IDE support is not perfect yet, but enough for productive work. I use plugin for IntelliJ IDEA, it works fine and stable enough, I don’t know why they still don’t release it :) Not all kinds of autocompletion works but.. “we use beta because it’s better than nothing” :)
More information about support in IDEs can be found here: https://www.rust-lang.org/ides.html
From my point of view, syntax is not the most strong part of Rust, but it’s the price I’m ready to pay for Rust’s features. Sometimes it’s over-verbose, especially when we talk about errors handling.
After some time I get used to new keywords. Initially I linked them with old terms I used to, and later I realized most of new terms are new for a reason, they bring new features into language and they don’t match one-to-one with old terms. Except “panic”. Ok, I know, it’s arguable thing, stay calm :)
Also two things could improve Rust: named arguments and arguments with default value, there is an issue on github about it.
It’s static and strong, with support of generics. After PHP, I needed just few hours to get used to strong typing and love it. I came to Rust mostly because of strong typing, way of returning results (Result/Some), awesome package manager and memory safety. Yes, I had to invest some time to learn it, to get used to it, but now, when I read my PHP code, I often wonder “Instance of what class it returns?.. What does mean maybe this one?? How the hell I was able to work with it??”. Really, good things are easy to get used to.
Absolutely best part of Rust is community. Friendly, civil and smart people, they helped me to solve a lot of my Rust issues and explained new things to me (thank you!). Authors of crates are responsive and ready to help and listen feedback. Rust itself evolving using RFCs, what allows community to make influence to Rust evolution.
Rust has builtin mechanism for documentation, and all the creates I tried, have documentation. Often with examples, sometime with detailed explanations.
nginx config is pretty simple:
Most tricky part for me was hashing passwords, solved by Scrypt module of Rust-Crypto.
For developer, coming from dynamic languages, Rust compiler removes half of reasons for tests — private functions refactoring safety (compiler gives it for free). Second half is a mix of logic errors, public API safety and remote resources communication, such as databases, files and external APIs. Rust has builtin testing features, so you can just do it from start, without additional dependencies.
Keep calm, avoid panics, don’t use unwrap (unwrap_or is ok) and your server will be very stable. I use println and redirect output to file, to create primitive logging, later I’m going to use something more smart and reliable.
As often happens in web servers world, the only bottleneck is DB communication - in our API it takes around 98% of every request. I know Rust is a very performant language, near to C++, but I use Rust not because of it. So there will be no table with sensational numbers, sorry. You just know performance is not an issue at all in Rust, and for web is more than enough :)
Declaration of nested structures is a pain — you can’t declare structure inside structure, so you need to declare separately all kinds of nested structures, and they nested structures, if such exists. With complex structures it takes a lot of code to describe JSON for them. Serde even has serde_codegen to help solve this issue. For simple structures all is pretty simple, and encoding is simple in both cases.
I use beanstalkapp and this script after files update:
This way you can just print nohup.out to read error messages.
Pros & Cons of web server in Rust
- Compilation step removes a lot of bugs so you save a lot of time, because debugging is much more expensive than writing code;
- Community will help if you struggle;
- Main tools are mature enough;
- Memory safety is a big thing for long-running servers;
- thread::spawn is a very handy thing, and safe in Rust.
- Learning curve is steep. Don’t give up, ask community when needed.
Some very simple things take more time to implement in strongly typed language, than in dynamically typed. For example: simple database edits and changing of response JSON structure. But it’s not a big thing as I worried initially — in most cases benefit of shorter debug period eliminates this difference.
Learning Rust, you will not only learn new APIs but also you will learn new way of thinking, errors handling, returning errors and data control.
Good luck and welcome aboard! :)
Removed rant about Result/Some when single branch needed, thanks for this comment.
Edited information about Intellij IDEA plugin, after update from authors:
Now it should work everywhere in a single crate. You’ll need to import project instead of just opening the project folder though, and you’ll also need a very recent nightly cargo for this to work. As a bonus, import will enable Go To Symbol (Ctrl+Shift+Alt+N) for dependencies.