Rust is great for writing web applications, actually.
While Rust has a reputation for being a low-level language and being difficult to work with, it’s proving to be superb for building application-level software too. I’ve been using Rust to build web servers and workers for moondip.xyz. With Rust, I’m building faster, and easily creating hyper-reliable and efficient services.
1. Building faster
Yes, wrestling with Rust’s compiler system can slow down development, especially when building prototypes. That being said, the compiler’s errors, which tend to be especially descriptive, create a short development feedback loop which ultimately speeds up development.
Specifically, the compiler catches bugs that would otherwise come up at runtime, either in development or, 😱, production. Here’s an example:
pub enum SomeEnum {
A,
B,
C,
D,
}
pub fn some_func(some_enum: SomeEnum) -> () {
...
match some_enum {
SomeEnum::A => // do something
SomeEnum::B => // do something else
}
...
}
The compiler will throw an error over this `match` statement because it doesn’t account for all of the possible values of the enum!
match some_enum {
SomeEnum::A => // do something
SomeEnum::B => // do something else
_ => // do something with all other values, such as throw an error
}
In this example, the compiler catches the error immediately, whereas in other languages, this could cause a bug when you run the code locally, or worse, after pushing to production — both requiring debugging.
Greater reliability
Again, the compiler’s strictness means that significantly fewer bugs make it to production. It also makes the task of eliminating exceptions, which blow up your program, fairly straight forward. For example, in order to access a value that might be undefined (in Rust, `None`), the developer is prompted to handle the case where the value is undefined.
Here are a few different ways of accessing an optional value:
fn some_func(some_str: Option<String>) -> Result<()> {
// Will cause an exception if some_str is None.
some_str.unwrap();
// Will also cause an exception, but with the given error message.
some_str.expect("Expected string in undefined.");
// Will set a default value if some_str is None.
let certain_str = some_str.unwrap_or(String::from("Some default value."))
// Will raise an error for the caller of `some_func()` to handle.
let certain_str = match some_str {
Ok(s) => s,
Err(err) => Err("some_str is undefined.")
}
}
Once again, Rust’s `match` statement serves as an ergonomic way to handle control flow. But notice, that while a developer can allow for exceptions using `unwrap( )` or `expected( )`, both methods stick out as red flags and Rust provides ergonomic alternatives.
As I’ve been building moondip.xyz, exceptions being thrown in production are extremely rare, in large part due to the compiler demanding this specificity when accessing optional values.
Greater Efficiency
Rust uses a tiny amount of CPU and memory resources compared to other popular languages for applications, such as Node or Python. This enables developers to run multiple services on a single node where other languages would require multiple nodes.
In practical terms, Rust can make your deployment infrastructure simpler and less expensive. Running Rust enables your team to push off addressing scaling issues and keep deployment costs low ($4/mo Digital Ocean virtual machines go a long way with Rust!).
Rust has a reputation for being a low-level language and being difficult to work with. While there’s truth to that, Rust is proving to be superb for building application-level software.