Back to overview

Rust

Reading time approx. 5 minutes
08.11.2023

The software industry never stands still. New technologies, frameworks and programming languages are constantly emerging. To ensure that our company is always up to date technologically, our Xperts regularly look at recent developments. This time, we took a closer look at the Rust programming language. We wanted to find out what the low-level, system-oriented language, which is very similar to C/C++, is all about.

What is Rust?

The development of Rust was supported by Mozilla Research in 2009. The motivation was to design a language that would enable developers to write small, fast code while avoiding memory issues. The language was named Rust in reference to the name of the rust fungi. This group of fungi is particularly known for being extremely robust and capable of surviving. Characteristics that also apply to Rust. Further development of the language has been an open source project for several years now. The Rustup toolchain installer is required for installation, and can also be used to update the Rust compiler and the Cargo build system.

Despite the high degree of abstraction to the actual hardware, Rust is still a system-oriented programming language. It manages without the overhead of a garbage collection mechanism. The high level of memory safety is certainly the biggest difference to classic system programming languages such as C or C++. Rust employs various mechanisms to prevent errors such as incorrect memory access, buffer overflows or race conditions. Static code analysis by the compiler plays an important role here. This also includes concepts such as standard immutability for variables and exclusive ownership of memory locations.

Compared to conventional languages, these fundamental concepts and techniques in Rust are new and not so familiar to many developers. In the following sections, we use two examples to illustrate some of the key aspects of Rust.

Examples

In Listing 1 we see an example program for the use of mutable and immutable variables.

Variables are generally declared as follows:

  • The keyword let is sufficient.
  • Optionally, you can also specify the type and assign a value.
  • If it is an immutable variable (read-only access), which is the case by default, a value can only be assigned once.
  • To declare a mutable variable (read and write access), the keyword mut must be used after let.
  • The validity of a variable is the surrounding statement block, delimited by curly brackets.

LISTING 1 VARIABLES AND THEIR VARIABILITY:

fn main() {
    let x : i32;
    x = 3;
    // x = 4; -> Compiler Error
    println!("{}", x); // -> 3

    let mut y = 5;
    y += 5;
    println!("{}", y); // -> 10
    {
        let y = y * 2; // -> 20
        println!("{}", y);
    }
    y += 1;
    println!("{}", y); // -> 11
}

Listing 2 shows the concepts of Ownership, Reference and Borrowing. Memory must be explicitly allocated and released here and rules managing ownership of memory must be adhered to. These are checked by the compiler. The rules are:

  • Every value in Rust has an owner.
  • There can only ever be one owner.
  • If the owner leaves the scope of validity, the value is deleted.

There are also rules for references:

  • At any point in time, there can be either one changeable reference or any number of unchangeable references.
  • References must always be valid.

To illustrate this, we will use the String data type. This is a non-scalar data type that manages the data allocated on the heap (another area of the memory in addition to the stack). A mutable variable is defined and the required memory is requested. When it is passed to a function, the value is transferred to the function. The variable leaves its area of validity and is no longer available for access.

Therefore, in this example, a copy of the variable is created and passed using Clone. It is also possible to use a reference as a function parameter. In this case, a reference to an object is used as a parameter instead of passing the ownership of the value. A reference is a pointer in that it is an address that points to the location of the data. References are generated with an & character. This is referred to as borrowing. In our use case, a mutable reference is passed to the function. Mutable references have one major restriction in particular: while there can only be exactly one write owner, any number of owners can be given read access. However, the value may no longer be changed after assignment.

LISTING 2: OWNERSHIP:

fn main() {

    let mut string_original = String::from("Original");
    ownership_with_drop_after(string_original.clone());
    println!("{string_original}"); // -> Original

    string_original = ownership_with_drop_after(string_original);
    println!("{string_original}"); // -> Original : Geändert

    println!("########"); 

    let mut string_reference_pointer = String::from("Pointer");
    change_with_pointer(&mut string_reference_pointer);
    println!("{string_reference_pointer}"); // -> Pointer Reference
}

fn ownership_with_drop_after (mut s : String) -> String {
    s.push_str(" : Geändert");
}

fn change_with_pointer(s : &mut String) {
    s.push_str(" Reference");
}

Conclusion

Compared to other programming languages, Rust offers particular advantages when it comes to stability, security and performance. However, getting started with Rust is not exactly easy due to the basic concepts of the language and the resulting architectures. It is therefore advisable not to switch to Rust completely at the beginning or to implement it prematurely in large projects.

Hybrid approaches in particular are proving to be useful, in which Rust components are developed and integrated as microservices or libraries. Another promising option involves generating WebAssembly bytecode using Rust, which has the potential to be highly beneficial in practical applications in the future. In this way, computationally intensive or low-level tasks can be handled efficiently in Rust.

Rust is also used in embedded systems. Thanks to the ownership concept, it guarantees a high level of memory security. As Rust does not require a runtime environment or garbage collector, it also has comparatively low resource consumption.