Rust by example - Getting Started: Introduction

4 minute read


:information_source: Note: Read the offical docs to get the most updated information.

Getting Started: Introduction

Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety. It aims to bring modern language design and an advanced type system. Rust does not use a garbage collector, using advanced static analysis to provide deterministic drops instead. It accomplishes this via the concept of ownership. Rust is most frequently used for applications where speed, performance and stability are essential.

Rust’s core and the standard library are intentionally minimal. Rustaceans are instead encouraged to add libraries, called crates, to the language by sharing them on crates.io.

Rust is a statically typed language, which means that it must know the types of all variables at compile time. The compiler can usually infer what type we want to use based on the value and how we use it.

The Rust language has a set of Keywords that are reserved for use by the language only, that you cannot use these words as names of variables or functions. You can find a list of the keywords in Appendix A.

If you’re using more than one word, the convention is to use snake_case and 4 spaces for indentation.

Expressions

Rust is an expression-based language:

  • Statements are instructions that perform some action and do not return a value, with a semicolon ;.
  • Expressions evaluate to a resultant value, with { } and no semicolon.

You can’t write x = y = 6 Expressions can be part of statements. Calling a function is an expression. Calling a macro is an expression.

fn main() {
    let y = {
        let x = 3;
        x + 1 // no semicolon
    };

    println!("The value of y is: {y}");
}

A new scope block created with curly brackets {} is an expression, for example:

    {                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward
        // do stuff with s
    }

Variables

Assigning a value to a name is referred to as a binding. Bindings are immutable unless declared with the mut keyword, meaning once we give the variable a value, the value won’t change. As Rust is a statically-typed language, each binding has a type known at compile-time.

Bindings are most commonly defined using the let keyword.

// Automatically inferred type
let fingers = 10;
let mut bananas = 5; // mutable

This line creates a new variable named fingers and binds it to the value 10.

// create a mutable variable that is currently bound to a new, empty instance of a String
let mut guess = String::new();

The equal sign = tells Rust we want to bind something to the variable now. The String::new() is a function that returns a new instance of a String. The :: syntax indicates that new is an associated function (a function that’s implemented on a type) of the String type. This new function creates a new, empty string.

Constants

Like immutable variables, constants are values that are bound to a name and are not allowed to change, but:

  • You aren’t allowed to use mut with constants. Constants aren’t just immutable by default—they’re always immutable.
  • You declare constants using the const keyword and the type of the value must be annotated.
  • Constants can be declared in any scope, including the global scope, which makes them useful for values that many parts of code need to know about.
  • constants may be set only to a constant expression, not the result of a value that could only be computed at runtime.
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Constants are valid for the entire time a program runs, within the scope in which they were declared.

Shadowing

Rust allows us to shadow the previous value of a variable with a new one, letting us reuse the variable name rather than forcing us to create two unique variables. This feature is often used when you want to convert a value from one type to another type.

The second variable is what the compiler will see when you use the name of the variable, taking any uses of the variable name to itself until either it itself is shadowed or the scope ends.

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}
// output:
// The value of x in the inner scope is: 12
// The value of x is: 6

Shadowing is different from marking a variable as mut because we’ll get a compile-time error if we accidentally try to reassign to this variable without using the let keyword.

We are effectively creating a new variable when we use the let keyword again, we can change the type of the value but reuse the same name.

let spaces = "   ";
let spaces = spaces.len();
//  if we try to use mut for this we would get a compile-time error

Comments

Rust supports two types of comments. The keyword // indicates a single-line comment; everything following the keyword until the end of the line is ignored. The keywords /* and */ indicate a multi-line comment, but is idiomatic and good practice to prefer single-line comments.

Documentation

Rust also supports doc-comments, which show up in the generated documentation produced by cargo doc.

  • Outer doc comments are formed with the keyword ///, which acts identically to the // keyword.
  • Inner doc comments are formed with the keyword //!, which acts identically to the // keyword.
/// The `add` function produces the sum of its arguments.
fn add(x: i32, y: i32) -> i32 { x + y }

mod my_cool_module {
    //! This module is the bee's knees.
}

Doc comments can be of arbitrary length and contain markdown, which is rendered into the generated documentation.

Updated:

Leave a Comment