I recently got a new job, and one of the most used programming languages at the company is Rust. Before that, I was using Python for ten years, mostly for data engineering work. But now, I’m going to try out this new (to me) programming language. I often see articles on different platforms praising Rust, and I wanted to see if Rust really lived up to its reputation. Rust is very different from Python, so I’m not going to go into detail about what makes Rust unique in this article. As a beginner, I just wanted to get started as quickly as possible, hopefully with the shortest possible transition, get my work done with Rust as quickly as possible, and also evaluate my own learning ability. In a way, I’m more interested in the overall experience of using Rust rather than a specific list of features.
Setting up the development environment
Setting up your development environment is as simple as running a command in the terminal, following the example provided on the Rust website.
Once you think you have everything installed and configured, if you want to verify that Rust is installed correctly, simply create an empty project in an empty directory.
cargo new tutorial
cd tutorial
cargo run
Next, open the folder in a text editor. If you are new to VSCode, I recommend it because some of the extensions are helpful and the guides on how to use them are easy to start with. I recommend rust-analyzer as the only extension for VSCode.
Output and debugging
If you want to understand how the program is running, the first thing you need to do is to use the command line to understand what the program is doing and what it has accomplished.
In addition, you can use the regular debugger. On M1, I recommend using LLDB in Visual Studio Code, which not only works well, but is often easier than printing logs in the output.
Up to this point, Rust and Python are actually very similar, except that all commands are run via cargo run instead of calling a specific file, such as python3 somefile.py.
Alternatively, you can run cargo build and then run the file in target/debug/tutorial and get the same result. Next, if you copy the generated files to another location or another similar machine, it will also work without installing anything related to Rust.
Error Handling
It has to be acknowledged that there are always surprises in programming, and it is important to be able to handle them in a predictable way. One of the big challenges in programming is that it’s hard to take into account all the possible errors that can occur in a program, because just writing code can lead to errors.
“Everyone knows that debugging is twice as hard as writing a program. So how can you debug if you’ve used all your ingenuity in writing the code?”
— Brian W. Kernighan
In Python, we usually throw exceptions and do error handling via try/except methods. We run a piece of code, catch the exception by condition if something goes wrong, and put it into a generic exception if all the conditions don’t match. There are various different classes of exceptions, and while Python allows you to call non-existent functions in packages and generate exceptions at runtime, doing so in Rust doesn’t even pass compilation – Rust doesn’t allow any strange errors at runtime, eliminating a large class of less predictable errors.
Here’s an example that illustrates this approach in Rust, and I’ll explain it in Python terms.
In the above code, we create a custom exception that is thrown in the do_something function, and the main function checks for that exception. The code above is basically the same as try/exept, but with a few more samples (which are required, but a little hard to understand at my current level).
You might say “there must be a better way”, especially if you have a lot of Python experience, and indeed, we will have to use packages.
Using external packages
The biggest advantage of programming over other industries is the ability to use things that other people have built. If you plan to do error handling in your programs, there is a great package thiserror. the package manager for Rust is cargo.
The package in Rust is called crate, and is installed by editing the Cargo.toml file in the directory. In this case, we add thiserror = “1.0” after [dependencies] and that’s it.
The previous code can then be rewritten as follows.
Now the code looks normal. I prefer this approach to starting everything from scratch.
It took me four or five years to find the joy of programming in Python, so I’m willing to spend more time exploring the advanced features of Rust, which has many error-handling methods, and I like the simpler ones.
I intentionally skipped simple concepts like “what is enum?” “what does pub mean?” “What are those # tokens?” and so on, because you can understand what they mean just by running the code.
Everything seems to be working fine. So what about testing?
Writing tests
Testing should be done at both the unit test and integration test levels. There are several ways to implement this, and while you can put the test code in the same file as the Rust code (which is recommended by the official guidelines), I would prefer to organize all the test code in a separate folder, which would be less burdensome to read the code and take up less screen real estate when editing the file. And to be honest, I was in a different mood when writing the tests and writing the code.
One of the methods is as follows.
The test can then be run with cargo test and the result is as follows.
There’s a lot more to expand on, but to avoid overcomplicating things, let’s stop at the “Pareto optimal” (aka the 80/20 rule). This reminds me of pytest, a tool that instantly improves the comfort level.
Reading a file, running some code and writing to another file
Above, we discussed some of the most basic issues: exporting, debugging, using external packages, and testing. Next, let’s do something more efficient: we can write a program to work with local files. The following example will read a CSV file, calculate some values, and then output the result.
To implement the program, we need to add the following two lines of settings to Cargo.toml.
csv=”1.1″
serde={version=”1″, features=[“derive”]}
You can guess how the main() function should be written.
Of course, this program can do a lot more. If you have a very complex CSV file, you can call pandsa (pola.rs) in Rust to process the data. I need to look into this further, but it seems to be a very powerful and efficient way of processing.
I think Rust’s CSV processing is comparable to Python’s, except for its ability to automatically deserialize.
Finally, there are some tests we can add, which I won’t go into here.
Sending HTTP Requests
Next, let’s try sending a basic HTTP request and processing the results. Most of the requests now require JSON processing.
Add the following lines of code to Cargo.toml.
reqwest = { version = “0.11”, features = [“json”] }
tokio = { version = “1”, features = [“full”]}
serde_json = “1”
That’s it, we can now request data from the API. Using a combination of the two methods above, we can fetch the data, parse it with pola.rs, and then write the results to a CSV file while keeping it memory safe. Remember when Python needed a loop to do this? Rust does a great job of that.
I believe that the Rust ecosystem will grow to cover more and more use cases, and it will be easy to leverage existing crates to make this happen.
Using SQLite
While it may seem strange to mention SQLite in this article, I’ve developed programs that use SQLite a lot, and I like SQLite because it’s portable, very efficient, and requires no maintenance.
The question of manipulating SQLite with Python is always whether to use ORM or not. don’t get me wrong, SQLAlchemy is very good, but when doing very small operations, using it is like killing a chicken with a cow. And the complexity SQLAlchemy brings makes it unsuitable for small embedded devices.
Instead, Rust can shine in this area, and there are many examples on the web of how to use Rust to manipulate SQLite, all of which I think are very good.
As a simple example, don’t forget to add the following line of code to Cargo.toml.
rusqlite = { version = “0.28.0”, features = [“bundled”] }
This example is from the rusqlite crate. of course, this is just the tip of the iceberg. But by combining the above methods, you can achieve many useful features.
Summing up
All things considered, Rust is an excellent language with many great packages, and many thanks to the developers who invented the language and worked hard to contribute to it. Although this article is only a preliminary exploration of Rust, I hope it will be of interest to beginners to learn Rust.
When using a programming language for the first time, the focus is on figuring out what the language itself does, not on memorizing a complete glossary of terms. You don’t need to understand what borrowing, inheritance, or traits mean, but rather follow some introductory articles step-by-step.
It’s much harder to build something from scratch than it is to build something from one to ten.