Rust Basics Series #2 : Utiliser des variables et des constantes


  • Français


  • Dans le premier chapitre de la série, j’ai partagé mes réflexions sur les raisons pour lesquelles Rust est un langage de programmation de plus en plus populaire. J’ai également montré comment écrire le programme Hello World en Rust.

    Continuons ce voyage Rust. Dans cet article, je vais vous présenter les variables et les constantes du langage de programmation Rust.

    En plus de cela, je couvrirai également un nouveau concept de programmation appelé “observation”.

    L’unicité des variables de Rust

    Une variable dans le contexte d’un langage de programmation (comme Rust) est connue sous le nom de un alias à l’adresse mémoire dans laquelle certaines données sont stockées.

    Cela est également vrai pour le langage de programmation Rust. Mais Rust a une “fonctionnalité” unique. Chaque variable que vous déclarez est immuable par défaut. Cela signifie qu’une fois qu’une valeur est affectée à la variable, elle ne peut plus être modifiée.

    Cette décision a été prise pour s’assurer que, par défaut, vous n’ayez pas à prendre de dispositions particulières comme verrous tournants ou mutex pour introduire le multi-threading. Rouiller garanties concurrence sûre. Étant donné que toutes les variables (par défaut) sont immuables, vous n’avez pas à vous soucier d’un thread modifiant une valeur sans le savoir.

    Cela ne veut pas dire que les variables dans Rust sont comme des constantes parce qu’elles ne le sont pas. Les variables peuvent être définies explicitement pour permettre la mutation. Une telle variable est appelée un variable modifiable.

    Voici la syntaxe pour déclarer une variable dans Rust :

    // immutability by default
    // the initialized value is the **only** value
    let variable_name = value;
    
    // mutable variable defined by the use of 'mut' keyword
    // the initial value can be changed to something else
    let mut variable_name = value;

    🚧

    Bien que vous soyez autorisé à modifier la valeur d’une variable mutable, vous ne pouvez pas lui affecter la valeur d’un autre type de données.

    Cela signifie que si vous avez une variable mutable de type float, vous ne pouvez pas lui attribuer de caractère plus tard.

    Aperçu de haut niveau des types de données de Rust

    Dans l’article précédent, vous avez peut-être remarqué que j’ai mentionné que Rust est un langage fortement typé. Mais pour définir une variable, vous ne spécifiez pas le type de données, à la place, vous utilisez un mot-clé générique let.

    Le compilateur Rust peut déduire le type de données d’une variable en fonction de la valeur qui lui est attribuée. Mais cela peut être fait si vous souhaitez toujours être explicite avec les types de données et que vous souhaitez annoter le type. Voici la syntaxe :

    let variable_name: data_type = value;

    Certains des types de données courants dans le langage de programmation Rust sont les suivants :

    • Type entier: i32 et u32 pour les entiers 32 bits signés et non signés, respectivement
    • Type virgule flottante: f32 et f64nombres à virgule flottante 32 bits et 64 bits
    • Type booléen: bool
    • Type de caractère: char

    Je couvrirai les types de données de Rust plus en détail dans le prochain article. Pour l’instant, cela devrait suffire.

    🚧

    Rust n’a pas de transtypage implicite. Donc, si vous attribuez la valeur 8 à une variable avec un type de données à virgule flottante, vous serez confronté à une erreur de compilation. Ce que vous devriez attribuer à la place est la valeur 8. ou 8.0.

    Rust impose également qu’une variable soit initialisée avant que la valeur qui y est stockée ne soit lue.

    { // this block won't compile
        let a;
        println!("{}", a); // error on this line
        // reading the value of an **uninitialized** variable is a compile-time error
    }
    
    { // this block will compile
        let a;
        a = 128;
        println!("{}", a); // no error here
        // variable 'a' has an initial value
    }

    Si vous déclarez une variable sans valeur initiale et que vous l’utilisez avant de lui affecter une valeur initiale, le compilateur Rust lancera un erreur de temps de compilation.

    Bien que les erreurs soient ennuyeuses. Dans ce cas, le compilateur Rust vous oblige à ne pas commettre l’une des erreurs les plus courantes lors de l’écriture de code : les variables non initialisées.

    Messages d’erreur du compilateur Rust

    Écrivons quelques programmes où vous

    1. Comprendre la conception de Rust en effectuant des tâches “normales”, qui sont en fait une cause majeure de problèmes liés à la mémoire
    2. Lire et comprendre les messages d’erreur/avertissement du compilateur Rust

    Tester l’immuabilité des variables

    Écrivons délibérément un programme qui tente de modifier une variable mutable et voyons ce qui se passe ensuite.

    fn main() {
        let mut a = 172;
        let b = 273;
        println!("a: {a}, b: {b}");
    
        a = 380;
        b = 420;
        println!("a: {}, b: {}", a, b);
    }

    Ressemble à un programme simple jusqu’à la ligne 4. Mais à la ligne 7, la variable b–une variable immuable–sa valeur est modifiée.

    Notez les deux méthodes d’impression des valeurs des variables dans Rust. À la ligne 4, j’ai placé les variables entre accolades afin que leurs valeurs soient imprimées. À la ligne 8, je laisse les crochets vides et je fournis les variables comme arguments, style C. Les deux approches sont valables. (Sauf pour modifier la valeur de la variable immuable, tout dans ce programme est correct.)

    Compilons ! Vous savez déjà comment faire cela si vous avez suivi le chapitre précédent.

    $ rustc main.rs
    error[E0384]: cannot assign twice to immutable variable `b`
     --> main.rs:7:5
      |
    3 |     let b = 273;
      |         -
      |         |
      |         first assignment to `b`
      |         help: consider making this binding mutable: `mut b`
    ...
    7 |     b = 420;
      |     ^^^^^^^ cannot assign twice to immutable variable
    
    error: aborting due to previous error
    
    For more information about this error, try `rustc --explain E0384`.

    📋

    Le mot ‘binding’ fait référence au nom de la variable. C’est une simplification excessive, cependant.

    Cela démontre parfaitement la vérification robuste des erreurs de Rust et les messages d’erreur informatifs. La première ligne lit le message d’erreur qui empêche la compilation du code ci-dessus :

    error[E0384]: cannot assign twice to immutable variable b

    Cela signifie que le compilateur Rust a remarqué que j’essayais de réaffecter une nouvelle valeur à la variable b mais la variable b est une variable immuable. Cela cause donc cette erreur.

    Le compilateur identifie même les numéros exacts de ligne et de colonne où se trouve cette erreur.

    Sous la ligne qui dit first assignment to `b` est la ligne qui fournit de l’aide. Puisque je suis en train de muter la valeur de la variable immuable bon me dit de déclarer la variable b comme une variable mutable en utilisant le mut mot-clé.

    🖥️

    Implémentez un correctif par vous-même pour mieux comprendre le problème en question.

    Jouer avec des variables non initialisées

    Voyons maintenant ce que fait le compilateur Rust lorsqu’une valeur de variable non initialisée est lue.

    fn main() {
        let a: i32;
        a = 123;
        println!("a: {a}");
    
        let b: i32;
        println!("b: {b}");
        b = 123;
    }

    Ici, j’ai deux variables immuables a et b et les deux ne sont pas initialisés au moment de la déclaration. La variable a obtient une valeur assignée avant que sa valeur ne soit lue. Mais la variable bLa valeur de est lue avant qu’une valeur initiale ne lui soit affectée.

    Compilons et voyons le résultat.

    $ rustc main.rs
    warning: value assigned to `b` is never read
     --> main.rs:8:5
      |
    8 |     b = 123;
      |     ^
      |
      = help: maybe it is overwritten before being read?
      = note: `#[warn(unused_assignments)]` on by default
    
    error[E0381]: used binding `b` is possibly-uninitialized
     --> main.rs:7:19
      |
    6 |     let b: i32;
      |         - binding declared here but left uninitialized
    7 |     println!("b: {b}");
      |                   ^ `b` used here but it is possibly-uninitialized
      |
      = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
    
    error: aborting due to previous error; 1 warning emitted
    
    For more information about this error, try `rustc --explain E0381`.

    Ici, le compilateur Rust génère une erreur de compilation et un avertissement. L’avertissement indique que la variable bLa valeur de n’est jamais lue.

    Mais c’est saugrenu ! La valeur de variable b est accessible sur la ligne 7. Mais regardez attentivement; l’avertissement concerne la ligne 8. C’est déroutant ; sautons temporairement cet avertissement et passons à l’erreur.

    Le message d’erreur indique que used binding `b` is possibly-uninitialized. Comme dans l’exemple précédent, le compilateur Rust indique que l’erreur est causée par la lecture de la valeur de la variable b à la ligne 7. La raison pour laquelle la lecture de la valeur de la variable b est une erreur est que sa valeur n’est pas initialisée. Dans le langage de programmation Rust, c’est illégal. D’où l’erreur de compilation.

    🖥️

    Cette erreur peut être facilement résolue en échangeant les codes des lignes 7 et 8. Faites-le et voyez si l’erreur disparaît.

    Exemple de programme : Échanger des numéros

    Maintenant que vous êtes familiarisé avec les problèmes courants liés aux variables, examinons un programme qui échange les valeurs de deux variables.

    fn main() {
        let mut a = 7186932;
        let mut b = 1276561;
    
        println!("a: {a}, b: {b}");
    
        // swap the values
        let temp = a;
        a = b;
        b = temp;
    
        println!("a: {}, b: {}", a, b);
    }

    Ici, j’ai déclaré deux variables, a et b. Les deux variables sont modifiables car je souhaite modifier leurs valeurs ultérieurement. J’ai attribué des valeurs aléatoires. Au départ, j’imprime les valeurs de ces variables.

    Ensuite, à la ligne 8, je crée une variable immuable appelée temp et lui attribuer la valeur stockée dans a. La raison pour laquelle cette variable est immuable est que tempLa valeur de ne sera pas modifiée.

    Pour échanger des valeurs, j’attribue la valeur de la variable b variable a et sur la ligne suivante j’attribue la valeur de temp (qui contient la valeur de a) à variable b. Maintenant que les valeurs sont échangées, j’imprime les valeurs des variables a et b.

    Lorsque le code ci-dessus est compilé et exécuté, j’obtiens la sortie suivante :

    a: 7186932, b: 1276561
    a: 1276561, b: 7186932

    Comme vous pouvez le voir, les valeurs sont échangées. Parfait.

    Utiliser des variables inutilisées

    Lorsque vous avez déclaré des variables que vous avez l’intention d’utiliser sur toute la ligne mais que vous ne les avez pas encore utilisées, et que vous compilez votre code Rust pour vérifier quelque chose, le compilateur Rust vous en avertira.

    La raison de ceci est évidente. Les variables qui ne seront pas utilisées prennent inutilement du temps d’initialisation (cycle CPU) et de l’espace mémoire. S’il ne sera pas utilisé, pourquoi l’avoir dans votre programme en premier lieu ?

    Mais parfois, vous pouvez être dans une situation où la création d’une variable peut ne pas être entre vos mains. Dites quand une fonction renvoie plus d’une valeur et que vous n’avez besoin que de quelques valeurs. Dans ce cas, vous ne pouvez pas dire au responsable de la bibliothèque d’ajuster sa fonction en fonction de vos besoins.

    Ainsi, dans des moments comme celui-ci, vous pouvez avoir une variable qui commence par un trait de soulignement et le compilateur Rust ne vous donnera plus de tels avertissements. Et si vous n’avez vraiment pas besoin d’utiliser la valeur stockée dans ladite variable inutilisée, vous pouvez simplement la nommer _ (trait de soulignement) et le compilateur Rust l’ignorera également !

    Non seulement le programme suivant ne générera aucune sortie, mais il ne générera pas non plus d’avertissements et/ou de messages d’erreur :

    fn main() {
        let _unnecessary_var = 0; // no warnings
        let _ = 0.0; // ignored completely
    }

    Opérations arithmétiques

    Puisque les maths sont des maths, Rust n’innove pas là-dessus. Vous pouvez utiliser tous les opérateurs arithmétiques que vous auriez pu utiliser dans d’autres langages de programmation comme C, C++ et/ou Java.

    Une liste complète de toutes les opérations du langage de programmation Rust, ainsi que leur signification, peut être trouvée ici.

    Exemple de programme : un thermomètre rouillé

    Voici un programme typique qui convertit Fahrenheit en Celsius et vice versa.

    fn main() {
        let boiling_water_f: f64 = 212.0;
        let frozen_water_c: f64 = 0.0;
    
        let boiling_water_c = (boiling_water_f - 32.0) * (5.0 / 9.0);
        let frozen_water_f = (frozen_water_c * (9.0 / 5.0)) + 32.0;
    
        println!(
            "Water starts boiling at {}°C (or {}°F).",
            boiling_water_c, boiling_water_f
        );
        println!(
            "Water starts freezing at {}°C (or {}°F).",
            frozen_water_c, frozen_water_f
        );
    }

    Il ne se passe pas grand chose ici… La température Fahrenheit est convertie en Celsius et vice versa pour la température en Celsius.

    Comme vous pouvez le voir ici, étant donné que Rust n’autorise pas la conversion automatique des types, j’ai dû introduire une virgule décimale aux nombres entiers 32, 9 et 5. À part cela, cela ressemble à ce que vous feriez en C, C++ et/ ou Java.

    En tant qu’exercice d’apprentissage, essayez d’écrire un programme qui détermine le nombre de chiffres dans un nombre donné.

    Constantes

    Avec quelques connaissances en programmation, vous savez peut-être ce que cela signifie. Une constante est un type particulier de variable dont la valeur ne change jamais. Il reste constant.

    Dans le langage de programmation Rust, une constante est déclarée en utilisant la syntaxe suivante :

    const CONSTANT_NAME: data_type = value;

    Comme vous pouvez le voir, la syntaxe pour déclarer une constante est très similaire à ce que nous avons vu en déclarant une variable dans Rust. Il y a cependant deux différences :

    1. Un nom constant doit être dans SCREAMING_SNAKE_CASE. Tous les caractères majuscules et les mots séparés par un tiret bas.
    2. L’annotation du type de données de la constante est nécessaire.

    Variables vs constantes

    Vous vous demandez peut-être, puisque les variables sont immuables par défaut, pourquoi le langage inclurait-il également des constantes ?

    Le tableau suivant devrait vous aider à dissiper vos doutes. (Si vous êtes curieux et que vous voulez mieux comprendre ces différences, vous pouvez regarder mon blog qui montre ces différences en détail.)

    Un tableau qui montre les différences entre les variables et les constantes dans le langage de programmation Rust

    Exemple de programme utilisant des constantes : Calculer l’aire d’un cercle

    Voici un programme simple sur les constantes dans Rust. Il calcule l’aire et le périmètre d’un cercle.

    fn main() {
        const PI: f64 = 3.14;
        let radius: f64 = 50.0;
    
        let circle_area = PI * (radius * radius);
        let circle_perimeter = 2.0 * PI * radius;
    
        println!("There is a circle with the radius of {radius} centimetres.");
        println!("Its area is {} centimetre square.", circle_area);
        println!(
            "And it has circumference of {} centimetres.",
            circle_perimeter
        );
    }

    Et lors de l’exécution du code, la sortie suivante est produite :

    There is a circle with the radius of 50 centimetres.
    Its area is 7850 centimetre square.
    And it has circumference of 314 centimetres.

    Ombre variable dans Rust

    Si vous êtes un programmeur C++, vous savez déjà à quoi je fais référence. Lorsque le programmeur déclare une nouvelle variable portant le même nom qu’une variable déjà déclarée, on l’appelle variable shadowing.

    Contrairement à C++, Rust vous permet également d’effectuer un shadowing variable dans la même portée !

    💡

    Lorsqu’un programmeur occulte une variable existante, la nouvelle variable se voit attribuer une nouvelle adresse mémoire mais est référencée avec le même nom que la variable existante.

    Voyons comment cela fonctionne dans Rust.

    fn main() {
        let a = 108;
        println!("addr of a: {:p}, value of a: {a}", &a);
        let a = 56;
        println!("addr of a: {:p}, value of a: {a} // post shadowing", &a);
    
        let mut b = 82;
        println!("\naddr of b: {:p}, value of b: {b}", &b);
        let mut b = 120;
        println!("addr of b: {:p}, value of b: {b} // post shadowing", &b);
    
        let mut c = 18;
        println!("\naddr of c: {:p}, value of c: {c}", &b);
        c = 29;
        println!("addr of c: {:p}, value of c: {c} // post shadowing", &b);
    }

    Le :p à l’intérieur des accolades dans le println déclaration est similaire à l’utilisation %p en C. Il précise que la valeur est au format d’une adresse mémoire (pointeur).

    Je prends 3 variables ici. Variable a est immuable et est ombré sur la ligne 4. Variable b est modifiable et est également ombré sur la ligne 9. Variable c est modifiable mais à la ligne 14, seule sa valeur est mutée. Il n’est pas ombragé.

    Maintenant, regardons la sortie.

    addr of a: 0x7ffe954bf614, value of a: 108
    addr of a: 0x7ffe954bf674, value of a: 56 // post shadowing
    
    addr of b: 0x7ffe954bf6d4, value of b: 82
    addr of b: 0x7ffe954bf734, value of b: 120 // post shadowing
    
    addr of c: 0x7ffe954bf734, value of c: 18
    addr of c: 0x7ffe954bf734, value of c: 29 // post shadowing

    En regardant la sortie, vous pouvez voir que non seulement les valeurs des trois variables ont changé, mais que les adresses des variables masquées sont également différentes (vérifiez les derniers caractères hexadécimaux).

    L’adresse mémoire des variables a et b modifié. Cela signifie que la mutabilité, ou son absence, d’une variable n’est pas une restriction lors de l’observation d’une variable.

    Conclusion

    Cet article couvre les variables et les constantes du langage de programmation Rust. Les opérations arithmétiques sont également couvertes.

    En guise de récapitulatif :

    • Les variables dans Rust sont immuables par défaut mais la mutabilité peut être introduite.
    • Le programmeur doit spécifier explicitement la mutabilité des variables.
    • Les constantes sont toujours immuables quoi qu’il arrive et nécessitent une annotation de type.
    • L’ombrage variable déclare un nouveau variable portant le même nom qu’une variable existante.

    Génial! Bon allez avec Rust je crois. Dans le chapitre suivant, j’aborderai les types de données dans Rust. Restez à l’écoute.

    En attendant, si vous avez des questions, n’hésitez pas à me le faire savoir.

    Source

    Houssen Moshinaly

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code