I started using Rust just as an experiment, to understand how they actually made memory safe language without loosing execution performance?
As a software developer without any specific language preferences I sold on an idea If Rust compiler compiles code without any issue, it is unlikely that it will give Segmentation fault or there will memory leak. That is pretty cool isn’t it?
No segfaults, no uninitialized memory, no coercion bugs, no data races, no null pointers, no header files, no makefiles, no autoconf, no cmake, no gdb. What if all the problems of c/c++ were fixed with one swing of a magic wand? The future is here, people https://news.ycombinator.com/item?id=10886253
I’m not going into making holly wars, instead with this article I’ll try to explain my favorite things in Rust and what I learned after first 2 weeks of coding in Rust, which by the way improved my coding skills in all other programming languages!
So let’s get started! 🎉
1. Avoid mutable static declarations
Rust compiler is not allowing mutable static/global variable declarations, in fact, it allows only to declare non dynamic standard types (int, const string, float, etc…), which are obviously because of the known sizes. Sometimes we really need to keep some global variable to check the things inside every part of our application, but Rust compiler says “OK! check as match as you want, but don’t change static variable, it’s not safe!”
Static declarations generally is not thread safe, and because Rust language built on top an idea of having move semantics you can’t do that match if you will have non standard typed static variable. I’m not sure that was happened because of how language compiler is built or that was the design initially, but at first I’ve been surprised that I can’t declare my custom struct mutable object as a static/global variable, then when I got a concept of Rust’s memory management, that started to make sense to me.
// WRONG!!
static mut SOME_VALUE: SomeType = SomeType{abc: 10};
// Correct!
static SOME_VALUE: SomeType = SomeType{abc: 10};
Take a way: Do not declare static/global variables to change them later on in other languages as well, that may cause a problems (I know, I know you can handle that, skip this 😏).
2. Design your code with a Tree principle
Agin coming from Rust’s memory management it turns out throwing a lot of non connected Structs ending up to cause “Why I can’t declare my struct object here?”. This happens after few hundred lines of code, but when I faced that problem, I just started rethink how generally application code allocation should work to design code properly and play with Rust’s compiler rules.
Let’s say you have an application which is going to parse configuration file and using that information it is going to start HTTP server.
In Golang for example I would do some package called config with a global variable representing application configurations. Then I’ll just simply import that package and use that global variable. BUT after implementing it in Rust’s way, I really liked approach of not having that global variable, instead I need to make something like this:
struct Config {
...
}
struct MainApp {
config: Config,
...
}
impl MainApp {
fn new(config_file: &str) -> MainApp {
// Parsing config file and allocating Config object
}
}
Which is more nice looking code where you have single point main structure which is going to allocate, create and declare all other things which is needed to operate your application. In fact this is more likely to keep your code scalable, because you know which structure goes where! (BUT this is just my opinion)
3. Use everything that you are declaring
By default Rust’s compiler is giving warnings about non used variables, so if you want to pass your code compilation without any “non green” output’s, you have to delete non used variables/structs/traits, except if you really need them to be there, just to extend codebase and use them later on, there is a macros to tell Rust compiler that “I really need this, don’t give a warning about it.”. I know this exists also for Golang and C++ (by adding some compiler flags), but in general having this feature built in is helping a lot to keep code clean. I’ve been missing this type of feature in many programming languages.
After experiencing “non used …” warnings in Rust, I just started to configure JavaScript’s ESLinter to give an errors for non used variables, same with Python linter etc… I know some of you may be doing this even before Rust, but personally for me that was the case coming from Rust.
4. Make human readable error messages
The best thing that I liked from the first time when I’ve started to run Rust compiler, it’s error messages! They are just amazingly helpful and very informative, especially if you are coming from C++, sometimes GCC compiler making your debugging even more messier by giving unhelpful error messages. For example this error message is giving a solution with an information to exact problem, by marking where exactly that happening
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
# cargo build
error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable
--> error.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- use `&mut String` here to make mutable
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ cannot borrow as mutable
By dealing with rust compiler, I started to make my application errors with this quality, and that changed the way how we started to deal with problems inside our team.
5. Write higher level abstractions as match as you can
In many languages higher level abstraction means less performance, but that’s not the case for Rust. In fact you will visit their home page, first thing they are prude of is “Zero cost of abstractions”. They did a lot of compiler optimizations for that and used non OOP principle to help designing that zero cost abstractions (with macros declarations as well).
But when I’m writing code in other languages after Rust, by default I’m trying to make more expressive abstractions, because that’s the key of having scalable and more readable codebase. Even for basic array operations Rust is giving map, filter, zip, etc… abstractions to prevent multiline loops and iterator declarations. Of course that’s done inside that abstractions, BUT they did that in more optimized way that you will do in your code.
// Calculate the intermediate sum of this segment:
let result = data_segment
// iterate over the characters of our segment..
.chars()
// .. convert text-characters to their number value..
.map(|c| c.to_digit(10).expect("should be a digit"))
// .. and sum the resulting iterator of numbers
.sum();
Making this kind of abstractions in our Python, Go or JS code gave use more better way of designing applications, and new hired people started to learn code faster, because they are mostly just re-using our abstractions.
This all 5 points based on my personal experience after 6 months of writing production Rust application.
Rust is not the main programming language for me and our team, but we learned a lot from it, especially how to write more efficient and scalable codebase in other programming languages.
So if you are a C++ fun, an thinking that Rust is just a shitty thing to spend time on it, I’m sure you are right, but why not to try then give an arguments. I will really like to hear your opinion.
Thanks for reading! 💥 , give a 👏 and share this article or let me know what do you think about this topic.