Écrire le programme Milestone Rust


  • Français


  • Depuis si longtemps, nous avons couvert une poignée de sujets fondamentaux sur la programmation en Rust. Certains de ces sujets sont les variables, la mutabilité, les constantes, les types de données, les fonctions, les instructions if-else et les boucles.

    Dans le dernier chapitre de la série Rust Basics, écrivons maintenant un programme en Rust qui utilise ces sujets afin que leur utilisation dans le monde réel puisse être mieux comprise. Travaillons sur un relativement simple programme pour commander des fruits d’un magasin de fruits.

    La structure de base de notre programme

    Commençons d’abord par saluer l’utilisateur et l’informer sur la façon d’interagir avec le programme.

    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Please select a fruit to buy.\n");
        
        println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
        println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
    }
    

    Obtenir l’entrée de l’utilisateur

    Le code ci-dessus est très simple. Pour le moment, vous ne savez pas quoi faire ensuite car vous ne savez pas ce que l’utilisateur veut faire ensuite.

    Ajoutons donc du code qui accepte l’entrée de l’utilisateur et la stocke quelque part pour l’analyser plus tard, et prenons l’action appropriée en fonction de l’entrée de l’utilisateur.

    use std::io;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Plase select a fruit to buy.\n");
        
        println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
        println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
        
        // get user input
        let mut user_input = String::new();
        io::stdin()
            .read_line(&mut user_input)
            .expect("Unable to read user input.");
    }

    Il y a trois nouveaux éléments dont je dois vous parler. Plongeons donc un peu dans chacun de ces nouveaux éléments.

    1. Comprendre le mot-clé “use”

    Sur la première ligne de ce programme, vous avez peut-être remarqué l’utilisation (haha !) d’un nouveau mot-clé appelé use. Le use mot-clé dans Rust est similaire au #include directive en C/C++ et la import mot-clé en Python. En utilisant le use mot-clé, nous “importons” le io module (entrée-sortie) de la bibliothèque standard Rust std.

    Vous vous demandez peut-être pourquoi importer le io module était nécessaire lorsque vous pouviez utiliser le println macro à sortir quelque chose à STDOUT. La bibliothèque standard de Rust a un module appelé prelude qui est automatiquement inclus. Le module prelude contient toutes les fonctions couramment utilisées qu’un programmeur Rust pourrait avoir besoin d’utiliser, comme le println macro. (Vous pouvez en savoir plus sur std::prelude module ici.)

    Le io module de la bibliothèque standard Rust std est nécessaire pour accepter l’entrée de l’utilisateur. Dès lors, un use déclaration a été ajoutée au 1St ligne de ce programme.

    2. Comprendre le type String dans Rust

    À la ligne 11, je crée une nouvelle variable mutable appelée user_input qui, comme son nom l’indique, sera utilisé pour stocker l’entrée de l’utilisateur sur la route. Mais sur la même ligne, vous avez peut-être remarqué quelque chose de nouveau (haha, encore !).

    Au lieu de déclarer une chaîne vide en utilisant des guillemets doubles sans rien entre eux (""), j’ai utilisé le String::new() fonction pour créer une nouvelle chaîne vide.

    La différence entre utiliser "" et String::new() est quelque chose que vous apprendrez plus tard dans la série Rust. Pour l’instant, sachez qu’avec l’utilisation du String::new() fonction, vous pouvez créer une chaîne qui est mutable et vit sur le tas.

    Si j’avais créé une chaîne avec "", j’obtiendrais quelque chose appelé “String slice”. Le contenu de la tranche String est également sur le tas, mais la chaîne elle-même est immuable. Ainsi, même si la variable elle-même est modifiable, les données réelles stockées sous forme de chaîne sont immuables et doivent être écrasé au lieu de modification.

    3. Acceptation de l’entrée de l’utilisateur

    Sur la ligne 12, j’appelle le stdin() fonction qui fait partie de std::io. Si je n’avais pas inclus le std::io module au début de ce programme, cette ligne serait std::io::stdin() au lieu de io::stdin().

    Le stdin() La fonction renvoie un handle d’entrée du terminal. Le read_line() La fonction saisit cette poignée d’entrée et, comme son nom l’indique, lit une ligne d’entrée. Cette fonction prend une référence à une chaîne mutable. Alors, je passe dans le user_input variable en la faisant précéder de &mutce qui en fait une référence mutable.

    ⚠️

    Le read_line() la fonction a un bizarrerie. Cette fonction arrête la lecture de l’entrée après l’utilisateur appuie sur la touche Entrée/Retour. Par conséquent, cette fonction enregistre également ce caractère de nouvelle ligne (\n) et une nouvelle ligne de fin est stockée dans la variable de chaîne mutable que vous avez transmise.

    Alors s’il vous plaît, tenez compte de cette nouvelle ligne de fin lorsque vous la traitez ou supprimez-la.

    Une introduction à la gestion des erreurs dans Rust

    Enfin, il existe une expect() fonction en bout de chaîne. Détournons un peu pour comprendre pourquoi cette fonction est appelée.

    Le read_line() La fonction renvoie un Enum appelé Result. J’aborderai les Enums dans Rust plus tard, mais sachez que les Enums sont très puissants dans Rust. Ce Result Enum renvoie une valeur qui informe le programmeur si une erreur s’est produite lors de la lecture de l’entrée utilisateur.

    Le expect() la fonction prend ceci Result Enum et vérifie si le résultat était correct ou non. Si aucune erreur ne se produit, rien ne se passe. Mais si une erreur s’est produite, le message que j’ai transmis ("Unable to read user input.") sera imprimé sur STDERR et le programme va sortir.

    📋

    Tous les nouveaux concepts que j’ai brièvement abordés seront couverts dans une nouvelle série Rust plus tard.

    Maintenant que vous comprenez, espérons-le, ces nouveaux concepts, ajoutons plus de code pour augmenter la fonctionnalité.

    Validation de l’entrée utilisateur

    J’ai sûrement accepté l’entrée de l’utilisateur mais je ne l’ai pas validée. Dans le contexte actuel, la validation signifie que l’utilisateur saisit une “commande” qui nous nous attendons à gérer. Pour le moment, les commandes sont de deux “catégories”.

    La première catégorie de commande que l’utilisateur peut saisir est le nom du fruit que l’utilisateur souhaite acheter. La deuxième commande indique que l’utilisateur souhaite quitter le programme.

    Notre tâche consiste donc maintenant à nous assurer que l’entrée de l’utilisateur ne s’écarte pas de la commandes acceptables.

    use std::io;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Plase select a fruit to buy.\n");
        
        println!("Available fruits to buy: Apple, Banana, Orange, Mango, Grapes");
        println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
        
        // get user input
        let mut user_input = String::new();
        io::stdin()
            .read_line(&mut user_input)
            .expect("Unable to read user input.");
            
        // validate user input
        let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
        user_input = user_input.trim().to_lowercase();
        let mut input_error = true;
        for input in valid_inputs {
            if input == user_input {
                input_error = false;
                break;
            }
        }
    }

    Pour faciliter la validation, j’ai créé un tableau de tranches de chaîne appelé valid_inputs (à la ligne 17). Ce tableau contient les noms de tous les fruits disponibles à l’achat, ainsi que les tranches de chaîne q et quit pour laisser l’utilisateur indiquer s’il souhaite quitter.

    L’utilisateur peut ne pas savoir comment nous attendons l’entrée. L’utilisateur peut taper “Apple” ou “apple” ou “APPLE” pour indiquer qu’il a l’intention d’acheter des pommes. Il est de notre devoir de gérer cela correctement.

    À la ligne 18, je coupe la nouvelle ligne de fin de user_input chaîne en appelant le trim() fonction dessus. Et pour gérer le problème précédent, je convertis tous les caractères en minuscules avec le to_lowercase() fonction de sorte que “Apple”, “apple” et “APPLE” finissent tous par “apple”.

    Maintenant à la ligne 19, je crée une variable booléenne mutable appelée input_error avec la valeur initiale de true. Plus tard à la ligne 20, je crée un for boucle qui itère sur tous les éléments (tranches de chaîne) de la valid_inputs tableau et stocke le motif itéré à l’intérieur du input variable.

    À l’intérieur de la boucle, je vérifie si l’entrée utilisateur est égale à l’une des chaînes valides, et si c’est le cas, je fixe la valeur de input_error booléen à false et sortir de la boucle for.

    Traiter les saisies invalides

    Il est maintenant temps de traiter une entrée invalide. Cela peut être fait en déplaçant une partie du code à l’intérieur d’une boucle infinie et continuer ladite boucle infinie si l’utilisateur donne une entrée invalide.

    use std::io;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Plase select a fruit to buy.\n");
        
        let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
        
        'mart: loop {
            let mut user_input = String::new();
    
            println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
            println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
    
            // get user input
            io::stdin()
                .read_line(&mut user_input)
                .expect("Unable to read user input.");
            user_input = user_input.trim().to_lowercase();
    
            // validate user input
            let mut input_error = true;
            for input in valid_inputs {
                if input == user_input {
                    input_error = false;
                    break;
                }
            }
    
            // handle invalid input
            if input_error {
                println!("ERROR: please enter a valid input");
                continue 'mart;
            }
        }
    }

    Ici, j’ai déplacé une partie du code à l’intérieur de la boucle et restructuré un peu le code pour mieux gérer cette introduction de la boucle. A l’intérieur de la boucle, sur la ligne 31, je continue le mart boucle si l’utilisateur a entré une chaîne invalide.

    Réagir à l’entrée de l’utilisateur

    Maintenant que tout le reste est géré, il est temps d’écrire du code sur l’achat de fruits sur le marché des fruits et d’arrêter quand l’utilisateur le souhaite.

    Puisque vous savez également quel fruit l’utilisateur a choisi, demandons combien il a l’intention d’acheter et informons-le du format d’entrée de la quantité.

    use std::io;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Plase select a fruit to buy.\n");
        
        let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
        
        'mart: loop {
            let mut user_input = String::new();
            let mut quantity = String::new();
    
            println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
            println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
    
            // get user input
            io::stdin()
                .read_line(&mut user_input)
                .expect("Unable to read user input.");
            user_input = user_input.trim().to_lowercase();
    
            // validate user input
            let mut input_error = true;
            for input in valid_inputs {
                if input == user_input {
                    input_error = false;
                    break;
                }
            }
    
            // handle invalid input
            if input_error {
                println!("ERROR: please enter a valid input");
                continue 'mart;
            }
            
            // quit if user wants to
            if user_input == "q" || user_input == "quit" {
                break 'mart;
            }
    
            // get quantity
            println!(
                "\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
    (Quantity of 1Kg 500g should be entered as '1.5'.)",
                user_input
            );
            io::stdin()
                .read_line(&mut quantity)
                .expect("Unable to read user input.");
        }
    }

    À la ligne 11, je déclare une autre variable mutable avec une chaîne vide et à la ligne 48, j’accepte l’entrée de l’utilisateur, mais cette fois la quantité dudit fruit que l’utilisateur a l’intention d’acheter.

    Analyser la quantité

    Je viens d’ajouter du code qui prend en quantité dans un format connu, mais ces données sont stockées sous forme de chaîne. Je dois en extraire le flotteur. Heureusement pour nous, cela peut être fait avec le parse() méthode.

    Tout comme le read_line() méthode, la parse() méthode renvoie le Result Enum. La raison pour laquelle le parse() méthode renvoie le Result Enum peut être facilement compris avec ce que nous essayons de réaliser.

    J’accepte une chaîne des utilisateurs et j’essaie de la convertir en flottant. Un flottant a deux valeurs possibles. L’un est la virgule flottante elle-même et le second est un nombre décimal.

    Alors qu’une chaîne peut avoir des alphabets, un flottant n’en a pas. Ainsi, si l’utilisateur saisit quelque chose autre que le [optional] virgule flottante et le(s) nombre(s) décimal(s), le parse() fonction renverra une erreur.

    Par conséquent, cette erreur doit également être gérée. Nous utiliserons le expect() fonction pour y faire face.

    use std::io;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Plase select a fruit to buy.\n");
        
        let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
        
        'mart: loop {
            let mut user_input = String::new();
            let mut quantity = String::new();
    
            println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
            println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
    
            // get user input
            io::stdin()
                .read_line(&mut user_input)
                .expect("Unable to read user input.");
            user_input = user_input.trim().to_lowercase();
    
            // validate user input
            let mut input_error = true;
            for input in valid_inputs {
                if input == user_input {
                    input_error = false;
                    break;
                }
            }
    
            // handle invalid input
            if input_error {
                println!("ERROR: please enter a valid input");
                continue 'mart;
            }
            
            // quit if user wants to
            if user_input == "q" || user_input == "quit" {
                break 'mart;
            }
    
            // get quantity
            println!(
                "\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
    (Quantity of 1Kg 500g should be entered as '1.5'.)",
                user_input
            );
            io::stdin()
                .read_line(&mut quantity)
                .expect("Unable to read user input.");
    
            let quantity: f64 = quantity
                .trim()
                .parse()
                .expect("Please enter a valid quantity.");
    
        }
    }

    Comme vous pouvez le voir, je stocke le float analysé dans la variable quantity en utilisant l’ombrage variable. Pour informer le parse() fonction dont l’intention est d’analyser la chaîne en f64je note manuellement le type de la variable quantity comme f64.

    Maintenant le parse() la fonction analysera la chaîne et renverra un f64 ou une erreur, que le expect() fonction traitera.

    Calcul du prix + retouches finales

    Maintenant que nous savons quel fruit l’utilisateur souhaite acheter et sa quantité, il est temps d’effectuer ces calculs maintenant et d’informer l’utilisateur des résultats/du total.

    Par souci de réalité, j’aurai deux prix pour chaque fruit. Le premier prix est le prix de détail, que nous payons aux vendeurs de fruits lorsque nous achetons en petites quantités. Le deuxième prix des fruits sera le prix de gros, lorsque quelqu’un achète des fruits en gros.

    Le prix de gros sera déterminé si la commande est supérieure à la quantité minimale de commande pour être considérée comme un achat en gros. Cette quantité minimale de commande varie pour chaque fruit. Les prix de chaque fruit seront en roupies par kilogramme.

    Avec cette logique à l’esprit, vous trouverez ci-dessous le programme dans sa forme finale.

    use std::io;
    
    const APPLE_RETAIL_PER_KG: f64 = 60.0;
    const APPLE_WHOLESALE_PER_KG: f64 = 45.0;
    
    const BANANA_RETAIL_PER_KG: f64 = 20.0;
    const BANANA_WHOLESALE_PER_KG: f64 = 15.0;
    
    const ORANGE_RETAIL_PER_KG: f64 = 100.0;
    const ORANGE_WHOLESALE_PER_KG: f64 = 80.0;
    
    const MANGO_RETAIL_PER_KG: f64 = 60.0;
    const MANGO_WHOLESALE_PER_KG: f64 = 55.0;
    
    const GRAPES_RETAIL_PER_KG: f64 = 120.0;
    const GRAPES_WHOLESALE_PER_KG: f64 = 100.0;
    
    fn main() {
        println!("Welcome to the fruit mart!");
        println!("Please select a fruit to buy.\n");
    
        let mut total: f64 = 0.0;
        let valid_inputs = ["apple", "banana", "orange", "mango", "grapes", "quit", "q"];
    
        'mart: loop {
            let mut user_input = String::new();
            let mut quantity = String::new();
    
            println!("\nAvailable fruits to buy: Apple, Banana, Orange, Mango, Grapes");
            println!("Once you are done purchasing, type in 'quit' or 'q'.\n");
    
            // get user input
            io::stdin()
                .read_line(&mut user_input)
                .expect("Unable to read user input.");
            user_input = user_input.trim().to_lowercase();
    
            // validate user input
            let mut input_error = true;
            for input in valid_inputs {
                if input == user_input {
                    input_error = false;
                    break;
                }
            }
    
            // handle invalid input
            if input_error {
                println!("ERROR: please enter a valid input");
                continue 'mart;
            }
    
            // quit if user wants to
            if user_input == "q" || user_input == "quit" {
                break 'mart;
            }
    
            // get quantity
            println!(
                "\nYou choose to buy \"{}\". Please enter the quantity in Kilograms.
    (Quantity of 1Kg 500g should be entered as '1.5'.)",
                user_input
            );
            io::stdin()
                .read_line(&mut quantity)
                .expect("Unable to read user input.");
            let quantity: f64 = quantity
                .trim()
                .parse()
                .expect("Please enter a valid quantity.");
    
            total += calc_price(quantity, user_input);
        }
    
        println!("\n\nYour total is {} Rupees.", total);
    }
    
    fn calc_price(quantity: f64, fruit: String) -> f64 {
        if fruit == "apple" {
            price_apple(quantity)
        } else if fruit == "banana" {
            price_banana(quantity)
        } else if fruit == "orange" {
            price_orange(quantity)
        } else if fruit == "mango" {
            price_mango(quantity)
        } else {
            price_grapes(quantity)
        }
    }
    
    fn price_apple(quantity: f64) -> f64 {
        if quantity > 7.0 {
            quantity * APPLE_WHOLESALE_PER_KG
        } else {
            quantity * APPLE_RETAIL_PER_KG
        }
    }
    
    fn price_banana(quantity: f64) -> f64 {
        if quantity > 4.0 {
            quantity * BANANA_WHOLESALE_PER_KG
        } else {
            quantity * BANANA_RETAIL_PER_KG
        }
    }
    
    fn price_orange(quantity: f64) -> f64 {
        if quantity > 3.5 {
            quantity * ORANGE_WHOLESALE_PER_KG
        } else {
            quantity * ORANGE_RETAIL_PER_KG
        }
    }
    
    fn price_mango(quantity: f64) -> f64 {
        if quantity > 5.0 {
            quantity * MANGO_WHOLESALE_PER_KG
        } else {
            quantity * MANGO_RETAIL_PER_KG
        }
    }
    
    fn price_grapes(quantity: f64) -> f64 {
        if quantity > 2.0 {
            quantity * GRAPES_WHOLESALE_PER_KG
        } else {
            quantity * GRAPES_RETAIL_PER_KG
        }
    }
    

    Par rapport à l’itération précédente, j’ai apporté quelques modifications…

    Les prix des fruits peuvent fluctuer, mais pour le cycle de vie de notre programme, ces prix ne fluctueront pas. Je stocke donc les prix de détail et de gros de chaque fruit dans des constantes. Je définis ces constantes en dehors du main() fonctions (c’est-à-dire globalement) car je ne calculerai pas les prix de chaque fruit à l’intérieur du main() fonction. Ces constantes sont déclarées comme f64 car ils seront multipliés par quantity lequel est f64. Rappel, Rust n’a pas de cast de type implicite 😉

    Après avoir enregistré le nom du fruit et la quantité que l’utilisateur souhaite acheter, le calc_price() La fonction est appelée pour calculer le prix dudit fruit dans la quantité fournie par l’utilisateur. Cette fonction prend le nom du fruit et la quantité comme paramètres et renvoie le prix comme f64.

    En regardant à l’intérieur du calc_price() fonction, c’est ce que beaucoup de gens appellent une fonction wrapper. On l’appelle une fonction wrapper car elle appelle d’autres fonctions pour faire son linge sale.

    Étant donné que chaque fruit a une quantité minimale de commande différente à considérer comme un achat en gros, pour garantir que le code puisse être maintenu facilement à l’avenir, le calcul du prix réel pour chaque fruit est divisé en fonctions distinctes pour chaque fruit individuel.

    Alors, tout ce que le calc_price() fonction est de déterminer quel fruit a été choisi et d’appeler la fonction respective pour le fruit choisi. Ces fonctions spécifiques aux fruits n’acceptent qu’un seul argument : la quantité. Et ces fonctions spécifiques aux fruits renvoient le prix comme f64.

    Maintenant, price_*() les fonctions ne font qu’une chose. Ils vérifient si la quantité commandée est supérieure à la quantité minimale de commande pour être considérée comme un achat en gros dudit fruit. S’il en est ainsi, quantity est multiplié par le prix de gros du fruit par kilogramme. Sinon, quantity est multiplié par le prix de détail du fruit au kilogramme.

    Puisque la ligne avec multiplication n’a pas de point-virgule à la fin, la fonction renvoie le produit résultant.

    Si vous regardez attentivement les appels de fonction des fonctions spécifiques aux fruits dans le calc_price() fonction, ces appels de fonction n’ont pas de point-virgule à la fin. Cela signifie que la valeur renvoyée par le price_*() les fonctions seront retournées par le calc_price() fonction à son appelant.

    Et il n’y a qu’un seul appelant pour calc_price() fonction. C’est à la fin du mart boucle où la valeur renvoyée par cette fonction est ce qui est utilisé pour incrémenter la valeur de total.

    Enfin, lorsque le mart boucle se termine (lorsque l’utilisateur saisit q ou quit), la valeur stockée dans la variable total est imprimé à l’écran et l’utilisateur est informé du prix qu’il doit payer.

    Conclusion

    Avec cet article, j’ai utilisé tous les sujets précédemment expliqués sur le langage de programmation Rust pour créer un programme simple qui démontre encore un peu un problème du monde réel.

    Maintenant, le code que j’ai écrit peut certainement être écrit d’une manière plus idiomatique qui utilise au mieux les fonctionnalités appréciées de Rust, mais je ne les ai pas encore couvertes !

    Alors restez à l’écoute pour le suivi Faites passer Rust au niveau supérieur de la série et apprenez-en plus sur le langage de programmation Rust !

    La série Rust Basics se termine ici. J’apprécie vos commentaires.

    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