Course 1: Rust Language

Rust Introduction: Cargo, Crates, Rust project, Hello Word, (2h on computers),

Pierre Cochard, Tanguy Risset

This course has been set up for student of the Telecommunication Department at INSA-Lyon (5th year), it is vastly inspired by the Rust book and many other resource on the web. It assumes that student do not have any programming experience in Rust by have a strong programming experience in other languages (C/C++ and object languages in particular).

In addition to this documents, you'll find other documents on Moodle presenting the concepts covered in this course. Don't forget to check it before you start. Many of the information listed here come from https://www.rust-lang.org/learn/.

The course is organize in sections that have questions. In addition you will find boxed text labeled course: which consists in important concept

Course: What is Rust and Why Rust

Why should computer science engineer always learn new languages?

Nowadays, Rust use is growing exponentially, the number of library and project using or useful to Rust developpers is already huge. The reason for that it that Rust provides safe memory management without a garbage collector, ensuring both performance and security. Its ownership system eliminates data races and segmentation faults. It enables efficient concurrent programming while guaranteeing memory safety. It is associated with a powerful modern ecosystem and and it is suited for embedded systems, system programming as well as high-performance applications. Adopted by major industry players, Rust is emerging as a reliable alternative for secure and system-level development.

Setting up the environnement for using Rust

We're going to start by setting up the environment that will enable you to program in Rust. We recommand that you use Rust on your own machine, but the environment is already installed on the departement computers.

This environment simply consists of having:

  1. An editor for programming, we strongly recommend Visual Studio Code that is available on all OS, for instance here: https://visualstudio.microsoft.com/fr/downloads/. See Appendix below for an introduction to Visual Studio Code.

  2. Install the cargo command with rustup.

cargo is the Rust compiler as well as the package manager and build system for Rust. cargo's installation and updating is itself managed by rustup.

Below is a summary for installing cargo on your laptop. The original complete instruction can be found here: https://doc.rust-lang.org/book/ch01-01-installation.html.

As a summary:

  • On linux or macOS, use the following command:

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

  • on Windows, go to https://www.rust-lang.org/tools/install and follow the instructions for installing Rust.

Course: What is cargo used for?

`cargo` is not only a compiler, it manages many aspects that you need to understand to use Rust correctly:
  • Dependency Management: Cargo manages the dependencies of a Rust project. It automatically downloads and builds the required libraries and dependencies, making it easier for developers to include external code in their projects.

  • Project Configuration: Cargo uses a file called Cargo.toml to configure a Rust project. This file includes information about the project, its dependencies, and various settings.

  • Building and Compilation: Cargo handles the compilation process of Rust code. It can build the project, manage dependencies, and generate executable binaries. Developers can use Cargo commands like cargo build to compile the project or cargo run to build and run it in one step.

  • Testing: Cargo provides built-in support for testing Rust code. Developers can use the cargo test command to run tests defined in the project.

  • Documentation: Cargo can generate and serve documentation for the project using the cargo doc command. This is useful for both internal and external documentation (the HTML file that you are reading has been generated by cargo doc)

  • Publishing Packages: Cargo facilitates the process of publishing Rust packages to the official package registry, called "Crates.io." This makes it easy for others to discover and use Rust libraries and projects.

Rust Hello World

A Rust project is contained in a directory which has the name of the project. From now on, we suggest making a projects directory in your home directory and keeping all your Rust projects there.

We will use the cargo command to build our first hello_world project. Note that this is not mandatory, everything can be built by hand, the Rust ompiler can be invoked without cargo by using the command rustc.

Execute this command
cargo new hello_world

Check the files generated. Cargo.toml is the project configuration file written in the TOML (Tom's Obvious, Minimal Language) format1, and src/main.rs is the Rust "main" file. if anything is unclear ask the teacher.

Build your project with the command:
cargo build
Where is the generated executable file? What is the bang (!) after println

Course: What about println!

As your can check on the Rust Standard Library documentation : println! is not a function, it is a macro.

Macros are called with a trailling bang (such as println!), they are a way of writing code that writes other code, which is known as metaprogramming. Understanding and declaring Macros is quite complex and will be seen later. But using them (such as using println!) is usually very easy.

Variables, Types ans Mutability

What is the problem with the program below? Does it compile?

let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");

What are the values printed by the program below? (Of course no one should do this kind of things.)

#![allow(unused)]
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}");
}

The notion of scope is quite important in Rust, a "scope" can be manipulated in the language as an object, we will see it in more detail in TD4.

Function in Rust

Functions in Rust are like in other languages. They are declared with the keyword fn, parameters are passed by value and an important specificity (borrowing values) will be study on next course.

Write a function fibonacci:
fibonacci(n: i32) -> i32
which computes element n of the fibonacci sequence.

We will not use a recursive solution, but rather a for loop whose syntax will be: for i in 2..n+1 ( half-open range) and mutable variables.

We recall the definition of the fibonacci function fib:

fib(0)=1
fib(1)=1
fib(i)=fib(i-1)+fib(i-2)  for i >= 

Generic types, traits and #derive directive

As a class in C++, types can be defined and can implement different methods. for instance, the following code defines the type Complex as a struct of two floats (as C++ classes, types begin with an Uppercase by convention).

#![allow(unused)]
fn main() {
struct Complex {
    re: f32,
    im: f32,
}

fn build_complex(re: f32,im:f32)->  Complex {
    Complex {re,im}
}       

let mut a = build_complex(2.3,4.0);
println!("a=({},{})",a.re,a.im);
a.re = a.re+1.;
println!("a=({},{})",a.re,a.im);
}

Course: Generic Types

As templates in C++, Rust enables the use of generic type in function or struct, enums or methods definitions. Here is a simple definition of a Point structure using integer or float coordinates:

#![allow(unused)]
fn main() {
struct Point<T> {
    x: T,
    y: T,
}

let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}

Modifier le programme de création de Complex ci dessus en utilisant un type struct Complex<T> utilisant un type générique

Course: Traits: Defining common behaviour

A trait defines a functionality that a particular type has and can share with other types. Traits are similar to a feature often called interfaces in other languages.

Many natural methods can be defined for any -- or at least many -- types. For instance the copy or clone methods (rust primitives) or the fmt method (of the trait std::fmt::Display, rust standard library) that enables to use println!. These methods are not defined by default when a new type is defined.

Traits are defined by the trait keyword. By convention they are named starting with an upper case, e.g. the trait Clone, it usually defines a method with the same name in lower case (here: clone())

If you want to clone a Complex, you juste have to write the implementation of the clone method:

impl Clone for Complex {
    fn clone(&self) -> Self{
        Complex{re: self.re, im: self.im}
    }
     // Now a.clone() can be used on Complex variables
}

Implement the Clone trait for the struct complex<T> type defined before. You will have to use impl<T: Clone> to ensure that the T generic type implements the Clone trait.

By the way, do you know the difference between copy and clone?

Select your prefered answer:

  • Clone is a supertrait of Copy2

  • Copy is implicit, inexpensive, and cannot be re-implemented (memcpy). Clone is explicit, may be expensive, and may be re-implemented arbitrarily.

  • The main difference is that cloning is explicit. Implicit notation means move for a non-Copy type.

Course: Deriving traits

For certain traits3: (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, etc.), the compiler is capable of providing basic implementations for some traits via the #[derive] attribute (In Rust, an attribute is metadata applied to code elements like functions, structs, modules, or crates, attributes are prefixed with # and enclosed in square brackets []).

These traits can still be manually implemented if a more complex behavior is required.

for instance, the Clone trait can be automatically derived for Complex type:

#[derive(Clone)]
struct Complex {
    re: f32,
    im: f32,
}

[... no need to implement Clone ...]

let a = build_complex(2.3,4.0);
let _c=a.clone()

[...]

How could we use println! to display Complex variables? Two methods, test both:

  1. implement the std::fmt::Display trait for type Complex. This will need to:
  • use std::fmt
  • search for the prototype of the Display trait
  • use the macro write! to print fields
  1. Derive the Debug trait that includes the fmt::Display trait and use the "{:?} format.

First example of "Move" semantic: the cube

In this first example we define a very simple data structure, a 'Cube' with a single field c that indicates the size of the cube.

Write a program Rust that defines a structure Cube and prints its size

Can you print te cube itself, i.e. can you write: println!("My cube: {}", Cube{c:0.5});? How to make the cube printable?

Hint: You can derive the std::fmt::Display trait or the Debug trait

define a variable x assigned to a given cube, print it and then define a second variable y defined by let y = x;. Then print x again, what is the problem?

Course: Move semantic

In Rust, move semantics refers to the ownership transfer of data from one variable to another. Rust enforces a strict ownership model where each piece of data has a single owner at a time, and ownership can be transferred (or "moved") when a value is assigned to another variable or passed as an argument to a function.

By default an assignement such as let y = x implies a transfer of ownership of the content of x to y. This ownership concept will be studied further in next course. This limit side effects: modifying y do not modify x.

In order to dupplicate the cube (as it would be done in any language), one has to clone it or to implement the Copy trait. Deriving the Copy trait for Cube changes the semantic of the assignement: the assignement is now a copy, not a move.

modify your program by cloning x into y

Introduction to Visual Studio Code

Visual Studio Code (often abbreviated as VS Code) is a cross-platform source code editor developed by Microsoft. It is compatible with Windows, macOS, and Linux, offering great flexibility to developers working in diverse environments. This lightweight yet powerful editor is designed to meet the needs of modern developers, providing a wide range of features.

Key Features of Visual Studio Code

Visual Studio Code stands out due to the following features:

  • Built-in support for multiple programming languages: VS Code supports a wide array of languages such as Python, JavaScript, C++, Java, and more, thanks to its extension system.

  • Extensions and customization: A vast library of extensions is available to add functionalities like debugging, version control, and language-specific tools.

  • Integrated debugger: VS Code provides an interactive debugging environment to simplify error correction in the code.

  • Version control integration: Seamless integration with Git and other version control systems allows developers to track code changes directly within the editor.

  • Integrated terminal: A terminal is available inside the editor, enabling command execution without leaving the application.

  • IntelliSense: This feature offers intelligent code completion and contextual suggestions based on syntax and variable types.

  • Cross-platform compatibility: VS Code works consistently on Windows, macOS, and Linux, ensuring a uniform user experience regardless of the operating system.

Thanks to its intuitive interface and powerful tools, Visual Studio Code has become one of the most popular editors among developers, whether they are beginners or experienced professionals. Its active community and frequent updates make it a reliable choice for addressing the evolving needs of software development.

How to use VS code efficiently for Rust (TODO)

  • installation avec apt sur linux, aller sur https://code.visualstudio.com/

  • lancer sur le répertoire projet

  • ajouter l'extension rust (barre de gauche, petit carrés), search rust -> install rust-analyzer

  • go to explorer

  • ctrl-shift-P pour la liste des commandes

1

You can have more documentation about the TOML format here: https://toml.io/en/ or here in french: https://toml.io/fr/. However, it is probably not necessary, TOML is quite simple to understand