To build web applications using the Rocket framework, we must first learn a bit about the Rust language since Rocket is built using that language. According to https://www.rust-lang.org, the Rust language is "a language empowering everyone to build reliable and efficient software." It began as a personal project for a programmer named Graydon Hoare, an employee at Mozilla, around 2006. The Mozilla Foundation saw the potential of the language for their product; they started sponsoring the project in 2009 before announcing it to the public in 2010.
Since its inception, the focus of Rust has always been on performance and safety. Building a web browser is not an easy job; an unsafe language can have very fast performance, but programmers working with system languages without adequate safety measures in place can make a lot of mistakes, such as missing pointer references. Rust was designed as a system language and learned many mistakes from older languages. In older languages, you can easily shoot yourself in the foot with a null pointer, and nothing in the language prevents you from compiling such mistakes. In contrast, in the Rust language, you cannot write a code that resulted in null pointer because it will be detected during compile time, and you must fix the implementation to make it compile.
A lot of the Rust language design is borrowed from the functional programming paradigm, as well as from the object-oriented programming paradigm. For example, it has elements of a functional language such as closures and iterators. You can easily make a pure function and use the function as a parameter in another function; there are syntaxes to easily make closures and data types such as Option
or Result
.
On the other hand, there are no class definitions, but you can easily define a data type, for example, a struct. After defining that data type, you can create a block to implement its methods.
Even though there is no inheritance, you can easily group objects by using traits. For example, you can create a behavior and name it the MakeSound
trait. Then, you can determine what methods should be in that trait by writing the method signatures. If you define a data type, for example, a struct named Cow
, you can tell the compiler that it implements a MakeSound
trait. Because you say the Cow
struct implements the MakeSound
trait, you have to implement the methods defined in the trait for the Cow
struct. Sounds like an object-oriented language, right?
The Rust language went through several iterations before a stable version was released (Rust 1.0) on May 15, 2015. Some of the early language design was scrapped before releasing the stable release. At one point, Rust had a class feature but this was scrapped before the stable release because Rust design was changed to have data and behavior separation. You write data (for example, in the form of a struct
or enum
type), and then you write a behavior (for example, impl
) separately. To categorize those impl
in the same group, we can make a trait. So, all the functionality you would want from an object-oriented language can be had thanks to that design. Also, Rust used to have garbage collection, but it was then scrapped because another design pattern was used. When objects get out of scope, such as exiting a function, they are deallocated automatically. This type of automatic memory management made garbage collection unnecessary.
After the first stable release, people added more functionalities to make Rust more ergonomic and usable. One of the biggest changes was async/await, which was released in version 1.39. This feature is very useful for developing applications that handle I/O, and web application programming handles a lot of I/O. Web applications have to handle database and network connections, reading from files, and so on. People agree that async/await was one of the most needed features to make the language suitable for web programming, because in async/await, the program doesn't need to make a new thread, but it's also not blocking like a conventional function.
Another important feature is const fn
, a function that will be evaluated at compile-time instead of runtime.
In recent years, many large companies have started to build a talent pool of Rust developers, which highlights its significance in business.
Why use the Rust language?
So, why should we use the Rust language for web application development? Aren't existing established languages good enough for web development? Here are a few reasons why people would want to use the Rust language for creating web applications:
- Safety
- No garbage collection
- Speed
- Multithreading and asynchronous programming
- Statically typed
Safety
Although writing applications using a system programming language is advantageous because it's powerful (a programmer can access the fundamental building block of a program such as allocating computer memory to store important data and then deallocating that memory as soon as it is not in use), it's very easy to make mistakes.
There's nothing in a traditional system language to prevent a program from storing data in memory, creating a pointer to that data, deallocating the data stored in memory, and trying to access the data again through that pointer. The data is already gone but the pointer is still pointing to that part of the memory.
Seasoned programmers might easily spot such mistakes in a simple program. Some companies force their programmers to use a static analysis tool to check the code for such mistakes. But, as programming techniques become more sophisticated, the complexity of the application grows, and these kinds of bugs can still be found in many applications. High-profile bugs and hacks found in recent years, such as Heartbleed, can be prevented if we use a memory-safe language.
Rust is a memory-safe language because it has certain rules regarding how a programmer can write their code. For example, when the code is compiled, it checks the lifetime of a variable, and the compiler will show an error if another variable still tries to access the already out-of-scope data. Ralf Jung, a postdoctoral researcher, already made the first formal verification in 2020 that the Rust language is indeed a safe language. Built-in data types, such as Option
or Result
, handle null-like behavior in a safe manner.
No garbage collection
Many programmers create and use different techniques for memory management due to safety problems. One of these techniques is garbage collection. The idea is simple: memory management is done automatically during runtime so that a programmer doesn't have to think about memory management. A programmer just needs to create a variable, and when the variable is not used anymore, the runtime will automatically remove it from memory.
Garbage collection is an interesting and important part of computing. There are many techniques such as reference counting and tracing. Java, for example, even has several third-party garbage collectors besides the official garbage collector.
The problem with this language design choice is that garbage collection usually takes significant computing resources. For example, a part of the memory is still not usable for a while because the garbage collector has not recycled that memory yet. Or, even worse, the garbage collector is not able to remove used memory from the heap, so it will accumulate, and most of the computer memory will become unusable, or what we usually call a memory leak. In the stop-the-world garbage collection mechanism, the whole program execution is paused to allow the garbage collector to recycle the memory, after which the program execution is resumed. As such, some people find it hard to develop real-time applications with this kind of language.
Rust takes a different approach called resource acquisition is initialization (RAII), which means an object is deallocated automatically as soon as it's out of scope. For example, if you write a function, an object created in the function will be deallocated as soon as the function exits. But obviously, this makes Rust very different compared to programming languages that deallocate memory manually or programming languages with garbage collection.
Speed
If you are used to doing web development with an interpreted language or a language with garbage collection, you might say that we don't need to worry about computing performance as web development is I/O bound; in other words, the bottleneck is when the application accesses the database, disk, or another network, as they are slower than a CPU or memory.
The adage might be primarily true but it all depends on application usage. If your application processes a lot of JSON, the processing is CPU-bound, which means it is limited by the speed of your CPU and not the speed of disk access or the speed of network connection. If you care about the security of your application, you might need to work with hashing and encryption, which are CPU-bound. If you are writing a backend application for an online streaming service, you want the application to work as optimally as possible. If you are writing an application serving millions of users, you want the application to be very optimized and return the response as fast as possible.
The Rust language is a compiled language, so the compiler will convert the program into machine code, which a computer processor can execute. A compiled language usually runs faster than an interpreted language because, in an interpreted language, there is an overhead when the runtime binary interprets the program into native machine code. In modern interpreters, the speed gap is reduced by using modern techniques such as a just-in-time (JIT) compiler to speed up the program execution, but in dynamic languages such as Ruby, it's still slower than using a compiled language.
Multithreading and asynchronous programming
In traditional programming, synchronous programming means the application has to wait until CPU has processed a task. In a web application, the server waits until an HTTP request is processed and responded to; only then does it go on to handle another HTTP request. This is not a problem if the application just directly creates responses such as simple text. It becomes a problem when the web application has to take some time to do the processing; it has to wait for the database server to respond, it has to wait until the file is fully written on the server, and it has to wait until the API call to the third-party API service is done successfully.
One way to overcome the problem of waiting is multithreading. A single process can create multiple threads that share some resources. The Rust language has been designed to make it easy to create safe multithreaded applications. It's designed with multiple containers such as Arc
to make it easy to pass data between threads.
The problem with multithreading is that spawning a thread means allocating significant CPU, memory, and OS resources, or what is colloquially known as being expensive. The solution is to use a different technique called asynchronous programming, where a single thread is reused by different tasks without waiting for the first task to finish. People can easily write an async program in Rust because it's been incorporated into the language since November 7, 2019.
Statically-typed
In programming languages, a dynamically-typed language is one where a variable type is checked at runtime, while a statically-typed language checks the data type at compile time.
Dynamic typing means it's easier to write code, but it's also easier to make mistakes. Usually, a programmer has to write more unit tests in dynamically-typed languages to compensate for not checking the type at compile time. A dynamically-typed language is also considered more expensive because every time a function is called, the routine has to check the passed parameters. As a result, it's difficult to optimize a dynamically-typed language.
Rust, on the other hand, is statically-typed, so it's very hard to make mistakes such as passing a string as a number. The compiler can optimize the resulting machine code and reduce programming bugs significantly before the application is released.
Now that we have provided an overview of the Rust language and its strengths compared to other languages, let's learn how to install the Rust compiler toolchain, which will be used to compile Rust programs. We'll be using this toolchain throughout this book.