Motivation
Software engineering is both my passion and my profession. I am a big fan of clean and intention-revealing software design. I am interested in everything that can help write quality and well-designed software solutions.
To clarify first, my goal with this post is not to start a war about programming languages. I view languages as tools within a broader toolkit, each with its own specific strengths and best use cases.
I tried many programming languages in my career while looking for the most elegant and cleanest one. I have used languages like Java, JavaScript, C#, C++, Python, PHP, Go, and Rust. Although all of them are powerful, I am the most impressed by the Rust programming language.
Rust is the most elegant programming language I have ever seen in my life. It natively solves many problems and design issues that can be frequently encountered in other languages, and a pain to work around. Although it is also not perfect and still in childhood, its potential is fascinating. Not to mention, Rust has been the most loved language by the developer community since 2015, according to Stack Overflow. This is no coincidence - Rust's innovative features and powerful capabilities make it a unique and standout language.
Rust is an open-source programming language, developed by Mozilla. It is a low-level language providing capabilities for doing system-level programming, similar to C++. It focuses on problem domains where speed, memory safety, and concurrency are the key considerations of an application. It is a compiled language with many unique language features that can not be found in any other language.
Learning Rust is not easy. It has a lot of new concepts, especially for developers who have experience in languages with higher-level abstractions, such as C# or JavaScript. It can take time for someone to be productive in Rust. Although Rust can be complex, its complexity serves a purpose. The language is designed to provide solutions for challenging problem domains, making it a powerful tool for developers. While Rust's complexity can be demanding, it also provides the satisfaction of solving difficult problems efficiently.
Let's jump into the details where I tell you why I fell in love with Rust.
Low-level language with high-level abstractions
One of the most mind-blowing things about Rust is that it can give a feeling of a high-level language, despite being a low-level system programming language. Rust offers rich patterns and syntax that help developers write clean, readable, and expressive code. Despite its low-level focus, Rust's elegant design allows it to be used efficiently for higher-level problem domains, including writing web APIs. The language is influenced by both Object-Oriented Programming (OOP) and Functional Programming (FP) paradigms, which all help together produce quality software.
Rust has an extensive set of language features that makes Rust enjoyable to use such as:
flexible enum types to model behavior
meta-programming to code generation
ownership feature to guarantee memory safety
pattern matching for clean condition handling
new type idiom for better encapsulation
alias type for better expressiveness
Rust also has strong support for functional-style programming. It provides us with a rich set of interfaces and abstractions, enabling writing code in a functional style. I am fascinated by the functional programming mindset and I use it in other languages, such as LINQ in C#, Streams in Java, and array instance methods in JavaScript. They allow me to solve the problem in a descriptive way, focusing on what to solve, instead of how to solve the problem. So focusing on the problem domain, instead of low-level technical details.
The compiler is your best friend
When you start writing code in Rust, do not be surprised if your code does not compile. The Rust compiler is a way different beast than compilers of other languages I used in the past. In some languages like JavaScript or C#, you find out if there's a problem with your code only when you run it. In Rust, your code just does not compile if something is wrong. The Rust compiler highlights many possible problems and flaws in the code, preventing issues such as memory leaks or race conditions. Moreover, it provides suggestions for how to fix errors in your code. A lot of bugs can be found in compile time thanks to this strict compiler. It is a self-documenting tool, and its aggressive behavior ensures correctness and safety before we run our program. It is not a joke when they say: a Rust developer is already happy when their code compiles. The Rust compiler behaves like a pair-programming buddy and can be a great source of knowledge along our coding journey.
The solution for the billion-dollar mistake
I consider having NULL value in a language as a poor language decision. And I am not the only one. The inventor of NULL Tony Hoare apologized for inventing NULL at a software conference called QCon London in 2009. He called it a billion-dollar mistake:
In 1965, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. - Tony Hoare | Null References: The Billion Dollar Mistake
NULL is a bad design choice in computer science that leads to poor coding and causes the well-known problem of NullPointerException. Dealing with the challenges posed by NULL values requires extra effort that is oftentimes overlooked. It is literally a billion-dollar mistake, making developers tear their hair.
The good thing is that Rust does not have a NULL value. It provides a native data structure called Option that encodes the concept of a value being present or absent:
Rust uses this structure everywhere, from its core libraries to third-party libraries and any production-grade applications. It is the backbone of handling NULL values, and it works exceptionally well in practice. It clearly reveals the intent of absent values, resulting in a more intuitive absent value handling.
Immutability by default
Immutability gives us strict control over our data, making it easier to understand, test, and trust. When we use immutable data types, we do not need to worry about how data is accessed, especially in a multi-threaded environment. No data locks, no concurrency issues, and no unexpected data modifications.
In Rust, all data types are immutable by default, ensuring safety and easy concurrency. If we want to create mutable data, we must explicitly declare it and use the mut keyword. By using the mut keyword we can convey our intent to peer developers about the potential change of the data, resulting in better code understanding and comprehension.
Enums and pattern matching
My favorite language features of Rust are enums and pattern matching. Both of them are powerful in their own way, but combined together they give us an elegant way to write clean code.
We have already seen an enum called Option before. Enums enable us to model the data in a descriptive and flexible way. While other languages also support Enums, Rust offers additional functionality by allowing data properties and different types to be associated with each variant. Look at this example:
Here we're modeling some entities related to the various payment methods in our application. Note that each enum variant holds a distinct type of data, except for the Cash variant which has no associated data. It is a flexible way to model our domain, isn't it? And you can associate as many types as you need to each enum variant.
As for pattern matching, it can be used with various types, both simple and complex, to help manage conditions and work with different data types. Gone are the days of messy if-else statements - the match operator is here to help! Next to readability, the other powerful thing with pattern matching is that it needs to be exhaustive, meaning that all the possible values should be handled when the pattern is matched. Neglecting to handle edge and corner cases is a recipe for disaster, as these are often the breeding ground for the nastiest bugs in our code. Rust ensures that we cover every possible value of the given structure, otherwise our code would not compile.
Here is an example using the above-mentioned PaymentMethod combined with pattern matching:
Solid error management
Many programming languages do not distinguish between recoverable and unrecoverable errors. There are only exceptions and try-catch statements, resulting in sloppy error management and a lack of separation of concerns. Rust also shines when it comes to error handling. It does not have exceptions. Instead, it uses the result-paradigm with a built-in standard type Result<T, E> for recoverable errors. This type makes modeling errors clean and intuitive. It encourages us to build robust code where the negative and edge cases are well understood and handled explicitly.
Here is the simplified source code for Result<T, E>:
It is a representation of both the potential outcome of an action and the potential errors that may occur. Although such abstraction can be easily created in other languages, in Rust this is the standard way to handle actions that can have recoverable and expected errors. It provides a shared interface for the whole ecosystem to standardize around. It has plenty of extension methods, enabling clean and ergonomic error management.
One of my favorite language features when it comes to error handling is the question mark '?' operator. It is responsible for propagating the error to the client of a given interface. First, let's look at the following function:
Initially, this function simply opens a file. Now imagine that you need to change this function to handle two cases: the happy case when the file can be opened successfully and propagates a success message to the client; and the negative case where something unexpected happens while opening the file. How would you do that in other languages? Or even here, how would you do that without prior knowledge of Rust? Possibly with some try-catch statement. But in Rust, you can use the “?” operator:
Notice the “?” operator at the end of the file-opening code. This operator helps to propagate any errors that occur to the method's caller in the form of a Result type that includes the error message. If the file can be opened without errors, then it just returns Ok with the success message. Elegant, isn't it? No try-catch and no ugly if-else statement! The “?” operator simplifies error handling and makes code more readable and concise. And again, the good thing is that such Result types are returned from all over the core and 3rd-party libraries of Rust, resulting in consistent error handling throughout the whole ecosystem.
Cargo the swiss army knife
Cargo is the standard Rust tool:
creating and building Rust projects
package and dependency management
publishing artifacts
linter and profiler
documentation generator
running performance benchmarks
writing and executing unit and integration tests.
Cargo is the swiss army knife of Rust. There are not so many programming languages out there where you have all these functionalities within one dedicated standard tool. Cargo also has an amazing feature called doc test that supports executing documentation examples as tests. I'm sure many of us can relate to encountering outdated documentation for libraries we've worked with in the past. Frustrating moments. But the doc test feature can help to turn our code examples into live and executable documentation, which ensures that the documentation is always up-to-date and working properly. All in all, Cargo addresses various engineering concerns that may arise during a project, resulting in easier, simplified, and more consistent software development.
A large spectrum of domains
Rust is designed for a wide range of problem areas that require both safety and performance. The main advantage of Rust is its ability to provide performance similar to that of the C++ programming language. Internet of Things (IoT), JavaScript toolings, and blockchain development are just a few of the many exciting areas where you can apply Rust. Many famous and big companies are already using Rust in production, such as:
Amazon
Microsoft
Discord
NPM
Facebook
Coursera
Figma
Rust also has strong support for WebAssembly (WASM), which is becoming increasingly important in modern web development. Utilizing Rust can open up many exciting possibilities for projects that may be less efficient or feasible in other programming languages.
Conclusion
Rust's rapid growth and adoption by major industry players speak volumes about its capabilities. With a robust set of tools, powerful libraries, and a supportive community, Rust is poised to become a major player in the future of software development. Although Rust has a steep learning curve, crafting quality software with it is a pleasure thanks to its rich patterns and syntax. Learning Rust can bring you joy and satisfaction, as well as a competitive edge that will position you at the forefront of this exciting technological revolution.
I looked at Rust a while back. It's the ugliest, most unreadable mashup of crap I've seen in a long time.
Nice, While rust or other language can help you build applications were performance is important but when it comes to developer happiness i see C# shine and now with .NET Core its performance is improved. The syntax is to the point, easy to understand compared to other languages like Rust, Go , C, Python etc, the visual studio tooling is good to navigate large code base, maintain code is easy. As of now there is no one language to meet all your needs. So use the right tool for the Job and accept the trade off.