rlim
Main image for Getting Started with Rust

Getting Started with Rust

written by Ricky Lim on 2025-09-21

Rust has become the secret recipe leveling up the performance of many tools in our Python ecosystem. You really can feel the boost like a goosebump ✨ when you start using these Rust-powered tools. Among them are: ruff 🐺 , uv ⚡, and polars ❄️ which are fired up by Rust and have been significantly 🚀 speed up our python workflows. ruff is used to format and lint your python code, and uv and polars can be thought as a faster, more ergonomic version of pip and pandas, respectively.

The performance boost I’ve experienced has made me genuinely excited to learn Rust 🦀. Coming from a python 🐍 background, I want to share my journey of getting started with Rust. So let's jump right in!

Installing Rust

The official documentation for Rust is rust-lang.org. It's like python.org for python.

First, we need to install rustup, which is a Rust installer

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Once installed, check for stable release with rustup update stable. Check the version with rustc --version.

With rustup, you can manage multiple Rust versions like with uv for python. For example, you can install a specific rust version for a project with:

rustup install 1.70.0
rustup override set 1.70.0

Once Rust is installed, it comes also with cargo, it's like pip for python but with a few extra features. ruff equivalent in Rust is cargo fmt to format the code, and cargo clippy is like ruff to lint the code. fmt is to ensure that our code follows a consistent style or for me It's a lazy shortcut: I set my editor to automatically format on save and let fmt do the right thing.

lint is always a confusing word for me, but in short it's a way to check for coding best practices. As a simple example if we have unused variables in our code, clippy will warn us. The nice thing, they also provide suggestions to fix the issue, which is to prefix the variable with an underscore _.

  --> src/main.rs:11:9
   |
11 |     let hello = "Hello";
   |         ^^^^^ help: if this is intentional, prefix it with an underscore: `_hello`
   |
   = note: `#[warn(unused_variables)]` on by default

Hello Rust

Let's create a simple hello world program, hello-rust. Our first step is to run cargo, which we'll use often with Rust.

# Start a new Rust project
cargo new hello-rust
cd hello-rust

The project is now created and we can directly go to src/main.rs. This is where our application code starts.

# Change to hello-rust
fn main() {
    println!("Hello, Rust!");
}

To run the program, cargo run. This is kind of similar to python main.py in python.

The difference between Rust and python is that Rust is a compiled language, whereas python is an interpreted language. In the interpreted language, the code is executed by Python interpreter so it can be run directly. With Rust, the code is first compiled into machine code by rustc (Rust compiler) in a build process.

The advantage of compiled Rust is that the resulting program runs independently, without needing the Rust compiler installed, making distribution easier. On the other hand, running Python code requires the Python interpreter to be installed on the machine or server. Rust compiler also helps catch many errors at compile time, making our code safe to run. With safety guarantee, the compiled code is run with lesser checks, thus boosting the performance of our code.

When we run cargo run, Rust first compiles the code into machine code, which you can find at target/debug/hello-rust, and then executes it. You should see Hello, Rust! printed in the terminal.

A small CLI application

Let's create a small CLI application that count the lines of a file.

First, we will add a dependency to our application. This is to help parsing the command line arguments, we will use clap crate. We can find the dependency in crates.io, which is the package registry for Rust, like pypi.org for python.

In our Cargo.toml similar to pyproject.toml in python, we will add the dependency.

# Add this to your Cargo.toml
[dependencies]
clap = { version = "4.0", features = ["derive"] }

Let's update our src/main.rs to use clap to parse the command line arguments.

use clap::Parser;
use std::fs::read_to_string;
use std::path::PathBuf;

#[derive(Parser)]
#[command(version, about = "Counting lines")]
struct Cli {
    path: PathBuf,
}

fn main() {
    let args = Cli::parse();

    let content = read_to_string(&args.path).expect("File cannot be read");
    let result = content.lines().count();
    println!("{}", result);
}

At the top of the file, we have a list of use statements like Python’s import. It brings external functionality into your code. The #[…] syntax is called an attribute in Rust. For now, you can think of it like a decorator (sort of) in Python. In this example, #[derive(Parser)] automatically generates code so that our Cli struct can parse command-line arguments. The #[command(version, about = "Counting lines")] attribute adds metadata for a nice help message, including version info and also description.

A struct in Rust is similar to a Python dataclass; it's a way to create a custom data type. Our struct Cli defines a custom data type similar to a Python dataclass, with PathBuf representing a filesystem path.

Rust introduces ownership rules for memory safety while Python uses a garbage collector. Here, we use a reference &args.path so that our function borrows the path for temporary use.

The expect method is used to handle errors gracefully. If read_to_string fails, the program will print "File cannot be read" instead of panicking with a generic message.

Running the application

Now we can run our application with cargo run -- <file-path>. The -- is used to separate the arguments for cargo and the arguments for our application.

For example, let's first check the version and help from our application.

$ cargo run -- --version
hello-rust 0.1.0

$ cargo run -- --help
Counting lines

Usage: hello-rust <PATH>

Arguments:
  <PATH>

Options:
  -h, --help     Print help
  -V, --version  Print version

Let's now give it a spin,

cargo run -- src/main.rs

It should print the number of lines in src/main.rs.

Huhu 🥳, so now we have written a CLI application in Rust to count lines with proper --help and --version flags. As you also may notice when run cargo run, we see that it is unoptimized.

  Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/hello-rust --help`

This is because we are running in debug mode and it is intended only for development.

When we are ready to ship our application, we can run cargo build --release to build the optimized version of our application. Now our application is shipped in target/release/hello-rust and we can run it directly with ./target/release/hello-rust <file-path>.

Key takeaways