Skip to content

Instantly share code, notes, and snippets.

@Kixunil
Last active February 4, 2023 19:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kixunil/ba491d01c17eecfe92de992b2fe04a73 to your computer and use it in GitHub Desktop.
Save Kixunil/ba491d01c17eecfe92de992b2fe04a73 to your computer and use it in GitHub Desktop.
WIP quick dive into a source code written in Rust

Quick dive into a source code written in Rust

Are you a Rust newbie who found some code written in Rust and wants to dive right into the code? You're at the right place! This quick guide is supposed to help you in exactly this situation. It's not meant to be a full Rust tutorial - there are beter sources of those!

Quick intro

Two basic things you should know before you start:

  • Rust strives to make writing reliable and performant code easier to write. These values are the most important and without them the language wouldn't be Rust but something else. Try to play along with it. If you can't you probably need to use another language.
  • Don't expect Rust to be as easy to learn as other languages! To achieve reliability and performance, Rust brings some novel or less-known concepts. Consider looking at least at those.

The structure of a Rust project

The best way to identify a Rust project is to look into top-level directory and check if it contains a file called Cargo.toml.

There are two main ways to structure a Rust project:

  • Single "crate" - a unit of compilation in a git repository. You can see this by Cargo.toml containing [package] section, usually at the top.
  • A workspace with multiple crates - sub-crates are in separate sub-directories Cargo.toml contains [workspace] section and a list of subcrates in members key. Note that each subdirectory also contains Cargo.toml

Each crate contains the main source code in src directory. Binary crates usually contain main.rs or bin directory with binaries. The binaries are occasionally explicitly specified in Cargo.toml. Library crates contain lib.rs file as the root of the code.

tests and examples are hopefully obvious. :)

After building (see below) there will be a new directory target with subdirectories debug and/or release that contain output of the build.

Tools

Your main tool when building or developing Rust code is cargo. Do not over-complicate it with other things. (Like directly running rustc, the official "Book" has bad advice here.) You can securely install it from your package repository (e.g. apt install cargo) however note that this is not the newest version of the language and some projects may fail to compile. You can install the newest version using rustup which is however a bit less secure. (Use a virtual machine if you're worried.)

If you use rustup I highly recommend running rustup component add clippy right after installation.

The most important cargo commands

cargo build - builds the code in debug mode. This almost always runs out-of-the-box. Some projects need extra dependencies - those tend to be documented in their README - check it if your build fails!

cargo build --release - build with optimizations - if you wonder why your code is slow, try this! If you're building a binary it's a good idea to add --locked flag which makes extra sure to use the same versions of libraries as the author specified.

cargo check - checks the code without building - very handy for quick iteration when you're fixing compile errors.

cargo clippy - check code with a shitload of additional lints - this will also teach you to become better at Rust! Not all projects enforce this so you may find some where running it will uncover issues with existing code. Asking the maintainer(s) about it is probably a good idea. (But check documentation and open issues first!)

cargo fmt - reformats your code to be consistent with community standard. Note that some people consider it too ugly/impractical so you may find projects that don't use it.

cargo test - builds and runs tests. Note that some projects may use something more involved (e.g. shell scripts). Check their documentation to see if that's the case.

Strange Rust concepts

As I mentioned earlier, Rust is difficult mainly because of new/uncommon concepts. I'm writing them here to get you off the ground quickly.

Sum types

Rust has so-called "sum types". They are enums which can contain additional data in each variant. En example of a sum type:

enum Vehicle {
    Car(Passengers),
    Truck(Cargo),
}

What's important here is that a value can not be both Car and Truck at the same time. If it's Car it can contain Passengers only and can not possibly contain Cargo. If it's Truck it's the other way around. Rust makes it mandatory to check which variant is there - you can not access data of incorrect variant by accident. Obviously, any number of variants is permitted (even one and zero; zero means no value can ever be constructed which is sometimes useful).

Here are two mmost important enums defined in the core library:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Option is used when dealing with values that may be empty (like null in some languages, but you can't forget to check it!). Result is used when handling fallible operations like opening a file.

These two types have a bunch of handy methods (yes, any type can have methods in Rust!). A very important ? operator works with them - it returns None or Err early from the function similarly to exceptions but as opposed to exceptions it's clearly visible which places may return.

Examples:

let value = hash_map.get("the answer to life, Universe and everything");
// This is how we check the value
if let Some(data) = value {
    println!("The answer is {}", data);
}

// Note the ? at the end - this will return if opening file failed!
let file = File::open("the_question.txt")?;

Mutability

All varibles in Rust are immutable by default. You can make a variable mutable by adding mut keyword. E.g. let mut my_variable = 42; Further, Rust makes sure you can't mutate same variable from multiple places at once. You must use explicit synchronization. Even in single thread. Yes, it does make sense, it prevents subtle bugs!

Ownership and borrowing

These are the most difficult concepts. Ownership is the idea that each value is owned by some speific piece of code (stack frame). The ownership is tracked at compile time so Rust knows at compile time where to insert destructors - when there is no owner.

You can allow other part of code access to an owned value using references - shared or mutable. This is called borrowing because the ownership is automatically returned. There's no run-time operation, it's compile-time concept!

When a value is mutably borrwoed (denoted &mut) it can not be simultaneously borrowed by anything else! There can be (obviously) multiple shared borrows (&) but they either can not mutate the value or synchronization must be performed. A typical way to synchronize across multiple threads is using Mutex. Single-thread synchronization uses Cell or RefCell but it's quite rare compared to other things.

Unsafe code

To implement all the protections Rust provides we need to start from some axioms that can not be proven by the compiler. These axioms are defined by the unsafe code. As a newbie you should avoid touching unsafe code! It's very risky (even easier to mess up than C/C++) and almost never needed. I recommend doing at least a few projects before working with unsafe. And if you do ask someone experienced to check your code!

Note that despite risks, unsafe code is not evil. If multiple skilled people review it it should be fine. Because unsafe code is rare doing deep review of it is actually feasible. Thus, don't judge a project based on the presence of unsafe code. Try figure out how the risk is managed instead.

  • Does the project use CI with fuzzing, miri or other sanitizers?
  • How many people reviewed the unsafe part of the code?
  • How skilled are those reviewers? (Tip: Ralf Jung is the top expert on unsafe - if he says something it's most likely correct. :) Not trying to imply he's infallible though!)

Conclusion

I hope this helped you get started. For more information read "The Book". Recommended authors of high-quality code: burntsushi, dtolnay

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment