TP 00 - Rust pour les débutants

Nous utiliserons le langage de programmation Rust pour les TPs SdE2.

Assignment

Vous devez accepter l'assignment d'ici est travailler avec ce repository: Lab0

Resources

  1. The Rust Programming Language, Chapitre 1, 2, 3 et 5 en, fr

Concepts basiques de langages de programmation pour Rust

Standard library

Par défaut, Rust a un ensemble d'éléments définis dans la bibliothèque standard qu'il introduit dans le cadre d'application de chaque programme. Cet ensemble s'appelle le prélude, et vous pouvez voir tout ce qu'il contient dans la documentation standard de la bibliothèque.

Si un type que vous voulez utiliser n'est pas dans le prélude, vous devez mettre ce type dans la portée explicitement avec une instruction use. L'utilisation de la bibliothèque std :: io vous offre un certain nombre de fonctionnalités utiles, notamment la possibilité d'accepter les entrées de l'utilisateur.

use std::io;

La fonction main

La fonction main est le entry point de notre programme.

fn main() {
    println!("Hello, world!");
}

On utilise println!() pour imprimer des messages sur l'ecran.

Pour insérer un placeholder dans le println! méthode, utilisez une paire d'accolades {}. Nous fournissons le nom ou l'expression de la variable pour remplacer le placeholder fourni en dehors de la chaîne.

fn main() {
 
    let name = "Mary";
    let age = 26;
 
    println!("Hello, {}. You are {} years old", name, age);
}

Variables et mutabilité

On utilise le mot-clé let pour créer un variable.

let a = 5;

Par défaut, en Rust, les variables sont immuables, ça veut dire, une fois qu'une valeur est liée à un nom, vous ne pouvez pas modifier cette valeur.

Exemple:

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

Dans ce cas, on va obtenir un erreur de compilation parce qu’on essaye de modifier la valeur de x de 5 à 6, mais x est immuable, donc on ne peut pas faire cette modification.

Bien que les variables soient immuables par défaut, vous pouvez les rendre modifiables en ajoutant mut devant le nom de la variable. L'ajout de mut transmet également l'intention aux futurs lecteurs du code en indiquant que d'autres parties du code modifieront la valeur de cette variable.

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

Maintenant, la valeur de x peut devenir 6.

Constantes

Comme les variables immuables, les constantes sont des valeurs qui sont liées à un nom et ne sont pas autorisées à changer, mais il existe quelques différences entre les constantes et les variables.

Tout d'abord, vous n'êtes pas autorisé à utiliser mut avec des constantes. Les constantes ne sont pas seulement immuables par défaut, elles sont toujours immuables. Vous déclarez des constantes en utilisant le mot clé const au lieu du mot clé let.

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Pour comprendre mieux, lire le chapitre 3 qui se trouve dans la documentation au début du TP!

Types des données

Types scalaire

Un type scalaire représente une valeur unique. Rust a quatre types scalaires principaux : entiers, nombres à virgule flottante, booléens et caractères.

Integer → Chaque variante peut être signée ou non signée et a une taille explicite.

let x: i8 = -2;
let y: u16 = 25;

Virgule flottante → Les types à virgule flottante de Rust sont f32 et f64, qui ont respectivement une taille de 32 bits et 64 bits. Le type par défaut est f64 car sur les processeurs modernes, c'est à peu près la même vitesse que f32 mais il est capable de plus de précision. Tous les types à virgule flottante sont signés.

fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
}

Boolean → Les booléens ont une taille d'un octet. Le type booléen dans Rust est spécifié à l'aide de bool.

let t = true;
let f: bool = false; // with explicit type annotation

Caractere → Le type char de Rust est le type alphabétique le plus primitif du langage.

let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
Types composés

Tuple → Un tuple est une manière générale de regrouper un certain nombre de valeurs avec une variété de types en un seul type composé. Les tuples ont une longueur fixe : une fois déclarés, leur taille ne peut pas augmenter ou diminuer.

let tup: (i32, f64, u8) = (500, 6.4, 1);

Array → Contrairement à un tuple, chaque élément d'un tableau doit avoir le même type. Contrairement aux tableaux de certains autres langages, les tableaux de Rust ont une longueur fixe.

let a = [1, 2, 3, 4, 5];

Pour comprendre mieux, lire le chapitre 3 qui se trouve dans la documentation au début du TP!

Fonctions

Nous définissons une fonction dans Rust en entrant fn suivi d'un nom de fonction et d'un ensemble de parenthèses. Les accolades indiquent au compilateur où commence et se termine le corps de la fonction.

fn main() {
    println!("Hello, world!");
 
    another_function();
}
 
fn another_function() {
    println!("Another function.");
}
Parametres

Nous pouvons définir des fonctions avec des paramètres, qui sont des variables spéciales qui font partie de la signature d'une fonction. Lorsqu'une fonction a des paramètres, vous pouvez lui fournir des valeurs concrètes pour ces paramètres.

fn main() {
    another_function(5);
}
 
fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Dans les signatures de fonctions, vous devez déclarer le type de chaque paramètre!

Déclarations vs. expressions

Les corps de fonction sont constitués d'une série d'instructions se terminant éventuellement par une expression.

Les déclarations sont des instructions qui effectuent une action et ne renvoient pas de valeur.

Les expressions évaluent une valeur résultante.

Pour comprendre mieux, lire le chapitre 3 qui se trouve dans la documentation au début du TP!

Fonctions avec valeurs de retour

Les fonctions peuvent renvoyer des valeurs au code qui les appelle. Nous ne nommons pas les valeurs de retour, mais nous devons déclarer leur type après une flèche (→). En Rust, la valeur de retour de la fonction est synonyme de la valeur de l'expression finale dans le bloc du corps d'une fonction. Vous pouvez revenir plus tôt à partir d'une fonction en utilisant le mot-clé return et en spécifiant une valeur, mais la plupart des fonctions renvoient implicitement la dernière expression.

fn five() -> i32 {
    5
}
 
fn main() {
    let x = five();
    println!("The value of x is: {x}");// "The value of x is: 5"
}

Pour comprendre mieux, lire le chapitre 3 qui se trouve dans la documentation au début du TP!

Flux de contrôle

If

Toutes les expressions if commencent par le mot-clé if, suivi d'une condition. Facultativement, nous pouvons également inclure une expression else.

fn main() {
    let number = 3;
 
    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

Vous pouvez utiliser plusieurs conditions en combinant if et else dans une expression else if:

fn main() {
    let number = 6;
 
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

Parce que si est une expression, nous pouvons l'utiliser sur le côté droit d'une instruction let pour affecter le résultat à une variable.

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };
 
    println!("The value of number is: {number}");//"The value of the number is 5"
}
Loop

Le mot-clé loop indique à Rust d'exécuter un bloc de code encore et encore pour toujours ou jusqu'à ce que vous lui disiez explicitement de s'arrêter.

fn main() {
    loop {
        println!("again!");
    }
}

L'une des utilisations d'une boucle est de réessayer une opération dont vous savez qu'elle pourrait échouer, comme vérifier si un thread a terminé son travail. Vous devrez peut-être également transmettre le résultat de cette opération hors de la boucle au reste de votre code. Pour ce faire, vous pouvez ajouter la valeur que vous souhaitez renvoyer après l'expression break que vous utilisez pour arrêter la boucle ; cette valeur sera renvoyée hors de la boucle afin que vous puissiez l'utiliser:

fn main() {
    let mut counter = 0;
 
    let result = loop {
        counter += 1;
 
        if counter == 10 {
            break counter * 2;
        }
    };
 
    println!("The result is {result}");
}
While
fn main() {
    let mut number = 3;
 
    while number != 0 {
        println!("{number}!");
 
        number -= 1;
    }
 
    println!("LIFTOFF!!!");
}
For
fn main() {
    let a = [10, 20, 30, 40, 50];
 
    for element in a {
        println!("the value is: {element}");
    }
}

Pour comprendre mieux, lire le chapitre 3 qui se trouve dans la documentation au début du TP!

Structures

Les structures sont similaires aux tuples, en ce sens qu'elles contiennent les deux plusieurs valeurs liées. Comme les tuples, les morceaux d'une structure peuvent être de différents types. Contrairement aux tuples, dans une structure, vous nommerez chaque élément de données afin que la signification des valeurs soit claire.

Pour définir une structure, nous entrons le mot-clé struct et nommons la structure entière. Le nom d'une structure doit décrire la signification des éléments de données regroupés. Ensuite, entre accolades, nous définissons les noms et les types des données, que nous appelons champs.

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

Pour utiliser une structure après l'avoir définie, nous créons une instance de cette structure en spécifiant des valeurs concrètes pour chacun des champs. Nous créons une instance en indiquant le nom de la structure, puis ajoutons des accolades contenant des paires clé : valeur, où les clés sont les noms des champs et les valeurs sont les données que nous voulons stocker dans ces champs.

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
}

Pour acceder a un certain membre du struct on utilise cette syntaxe:

fn main() {
    let mut user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
 
    user1.email = String::from("anotheremail@example.com");

Notez que l'instance entière doit être modifiable ; Rust ne nous permet pas de marquer uniquement certains champs comme mutables!

Comme pour toute expression, nous pouvons construire une nouvelle instance de la structure en tant que dernière expression dans le corps de la fonction pour renvoyer implicitement cette nouvelle instance.

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username: username,
        email: email,
        sign_in_count: 1,
    }
}
Tuple structs

Rust prend également en charge les structures qui ressemblent aux tuples, appelées tuple structs. Les structures de tuple ont la signification supplémentaire fournie par le nom de la structure mais n'ont pas de noms associés à leurs champs ; au lieu de cela, ils ont juste les types des champs. Les structures de tuple sont utiles lorsque vous souhaitez donner un nom à l'ensemble du tuple et lui donner un type différent des autres tuples, et lorsque nommer chaque champ comme dans une structure régulière serait verbeux ou redondant.

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
 
fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

Pour comprendre mieux, lire le chapitre 5 qui se trouve dans la documentation au début du TP!

String

Il existe 2 types de string en Rust: String et &str. Pour aujourd’hui on va utiliser String (qui fait partie de la bibliothèque std::string). Pour créer un String on utilise la méthode:

let s: String = String::from(“some string here”);

On va apprendre dans les prochains laboratoires quelles sont les différences entre String et &str et quand on doit utiliser chacun d’entre eux.

Exécuter le programme

On doit acceder a la directeur ou le fichier main.rs se trouve et executee la comande:

cargo run

Exercises

Si vous n'avez pas installé Rust, vous pouvez utiliser Rust Playground pour résoudre les sujets.

  1. Écrivez un programme qui imprime votre nom.
  2. Définissez deux variables et attribuez-leur une valeur numérique. Affiche la valeur maximale entre les deux sans utiliser de variable temporaire.
  3. Écrivez une fonction qui vérifie si un nombre est divisible par n.
  4. Définissez un tableau de nombres et écrivez le code pour en afficher la valeur maximale.
  5. Définissez une structure appelée Ordinateur qui définit la marque, le nom du processeur et la taille de la mémoire d'un ordinateur.
    1. Ecrivez une fonction associée (statique) appelée new qui crée une instance de la structure.
    2. Écrivez une méthode appelée display qui imprime toutes les informations.
  6. Définissez un tableau avec des éléments de type Ordinateur. Écrivez un programme qui affiche un menu avec les options suivantes: a. imprimer tous les ordinateurs, b. imprimer l'ordinateur avec la plus grande quantité de mémoire. Lisez les touches du clavier et exécutez l'option sélectionnée jusqu'à ce que vous lisez quelque chose de différent de a et b.

Hint: utilisez io::stdin().read_line(&mut buffer) pour lire a partir du clavier.

sde2/laboratoare/00_rust.txt · Last modified: 2023/02/28 08:04 by cristiana.andrei
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0