Cours accéléré ultime sur Rust | Nathan Stocks | Skillshare
Recherche

Vitesse de lecture


1.0x


  • 0.5x
  • 0.75x
  • 1 x (normale)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

Cours accéléré ultime sur Rust

teacher avatar Nathan Stocks, Rust, Python, and Indie Games

Regardez ce cours et des milliers d'autres

Bénéficiez d'un accès illimité à tous les cours
Suivez des cours enseignés par des leaders de l'industrie et des professionnels
Explorez divers sujets comme l'illustration, le graphisme, la photographie et bien d'autres

Regardez ce cours et des milliers d'autres

Bénéficiez d'un accès illimité à tous les cours
Suivez des cours enseignés par des leaders de l'industrie et des professionnels
Explorez divers sujets comme l'illustration, le graphisme, la photographie et bien d'autres

Leçons de ce cours

    • 1.

      Introduction

      3:29

    • 2.

      Aperçu des exercices

      0:55

    • 3.

      Cargo

      3:53

    • 4.

      Variables

      4:50

    • 5.

      Portée

      2:40

    • 6.

      Sécurité de mémoire

      1:33

    • 7.

      Exercice A - Variables

      4:55

    • 8.

      Fonctions

      2:06

    • 9.

      Exercice B - Fonctions

      3:23

    • 10.

      Système de module

      3:50

    • 11.

      Types scalaires

      4:41

    • 12.

      Types de composés

      2:08

    • 13.

      Exercice C - Types simples

      6:25

    • 14.

      Flux de contrôle

      5:23

    • 15.

      Strings

      4:43

    • 16.

      Exercice D - Flux de contrôle et d'ensembles

      4:28

    • 17.

      Propriétaire

      6:03

    • 18.

      Références et empruntant et emprunt

      4:42

    • 19.

      Exercice E - Propriétaire et références

      4:49

    • 20.

      Structures

      3:09

    • 21.

      Traits

      4:57

    • 22.

      Exercice F - Structs et traits

      2:47

    • 23.

      Collections

      2:54

    • 24.

      Enums

      7:18

    • 25.

      Exercice G - Collections et enums

      6:29

    • 26.

      Closures

      2:25

    • 27.

      Threads

      1:52

    • 28.

      Exercice H - Closures et fils

      7:57

    • 29.

      Invaders Partie 1 : Configurer l'audio

      4:25

    • 30.

      Invaders partie 2 : Rendu et Multithreading

      18:26

    • 31.

      Invaders partie 3 : Le joueur

      4:28

    • 32.

      Invaders partie 4 : Prise de vue

      9:01

    • 33.

      Invaders Partie 5 : Invaders

      13:00

    • 34.

      Invaders partie 6 : Gagner et perdre

      8:13

    • 35.

      Le mot de la fin

      0:31

  • --
  • Niveau débutant
  • Niveau intermédiaire
  • Niveau avancé
  • Tous niveaux

Généré par la communauté

Le niveau est déterminé par l'opinion majoritaire des apprenants qui ont évalué ce cours. La recommandation de l'enseignant est affichée jusqu'à ce qu'au moins 5 réponses d'apprenants soient collectées.

880

apprenants

5

projets

À propos de ce cours

Rejoignez Nathan Stocks pour un cours de a en pratique en pratique en cours d'apprentissage dans la poussée.

Rust est un langage de programmation des systèmes qui élimine les classes entières des bugs et des vulnérabilités de sécurité. Les zero-cost les systèmes sont de la plus grande zero-cost Aucune s'en acquire en soi dans des espaces aussi divers comme les moteurs de jeu, les appareils embarqués et la programmation Web ! Apprenez à écrire du code à haute performance sans l'inquiet des accrochages ou des vulnérabilités de sécurité. Rejoignez-vous à une communauté vibrante de développeurs qui a une de simples développeurs dans laquelle la divergences, l'inclusions et un bien être simple en étant clair en bon lieu.

Rencontrez votre enseignant·e

Teacher Profile Image

Nathan Stocks

Rust, Python, and Indie Games

Enseignant·e

Nathan Stocks has been a software developer for over 20 years. He fell in love with Rust in 2016 and began teaching it the following year. He experiments with Indie Game development in both Rust and more traditional game engines. He has used Python professionally for most of his career, and even wrote his own test runner called Green.

Nathan loves teaching Rust when he gets the chance, especially in person at conferences and corporate boot camps.

If Nathan had to pick his favorites, they would be: Rust, Python, PostgreSQL, Linux (server), macOS (desktop), vim and emacs, and whichever IDE has the best Rust support at the moment.

Nathan loves to spend time with his wife and kids, play frisbee, eat food, and play games. His ambition is to one day run his own so... Voir le profil complet

Level: Beginner

Notes attribuées au cours

Les attentes sont-elles satisfaites ?
    Dépassées !
  • 0%
  • Oui
  • 0%
  • En partie
  • 0%
  • Pas vraiment
  • 0%

Pourquoi s'inscrire à Skillshare ?

Suivez des cours Skillshare Original primés

Chaque cours comprend de courtes leçons et des travaux pratiques

Votre abonnement soutient les enseignants Skillshare

Apprenez, où que vous soyez

Suivez des cours où que vous soyez avec l'application Skillshare. Suivez-les en streaming ou téléchargez-les pour les regarder dans l'avion, dans le métro ou tout autre endroit où vous aimez apprendre.

Transcription

1. Introduction: Bienvenue sur le cours ultime contre la rouille. Laissez-moi me présenter. Je m'appelle Nathan Stocks. J' ai passé une vingtaine d'années à travailler dans le développement de logiciels, principalement sur l'infrastructure back-end. Bien que j'ai un travail de jour, je clair de lune en tant que développeur de jeux indépendant sur agileperception.com et comme instructeur de cours en ligne enseignant Rust, qui est un langage de systèmes fantastique que j'aime. Puisque vous êtes ici, je parie que vous êtes intéressé par la rouille aussi ! Peu importe d'où tu viens, Rust a quelque chose pour toi. Vous n'êtes peut-être pas sûr que Rust soit la bonne chose pour vous. Vous avez peut-être l'intention d'être comme l'un de ces cinéphiles, et vous voulez juste regarder et écouter aujourd'hui et voir si les choses semblent intéressantes. C' est bon, il y a un temps pour ça ! J' ai été là... mais si tu veux vraiment apprendre Rust aujourd'hui, je veux que tu fasses quelque chose avec ce que tu apprennes. Au moins, faites les exercices ! Mieux encore, faites votre propre petit projet et expérimentez avec des choses. Vous apprendrez en faisant — et l'apprentissage est important parce que Rust a une courbe d'apprentissage très abrupte au début. Sans apprendre les fondamentaux, vous ne serez même pas en mesure d'obtenir votre code à compiler - et les fondamentaux sont exactement ce que nous apprenons aujourd'hui. C' est un cours de crash dans toutes les choses que vous devez savoir pour pouvoir vraiment commencer à utiliser et apprendre Rust. Je vais également vous apprendre à trouver des réponses par vous-même, afin qu'après ce cours, vous sachiez où aller pour en savoir plus sur les sujets qui vous intéressent. N' importe qui peut apprendre Rust. N' importe qui peut être un programmeur de systèmes. Amusez-vous avec ce que vous savez et apprenez un peu plus chaque jour. Ok, donc Rust est génial ! Parlons de ce qu'est Rust, d'où elle vient, et pourquoi c'est si génial. Rust est un langage de programmation système qui poursuit le trifecta : la sécurité, qui est garantie au moment de la compilation ; concurrence intrépide, ce qui est beaucoup plus facile quand les choses sont sûres ; et la vitesse fulgurante grâce aux abstractions à coût zéro et autres choses agréables. langages de script de haut niveau comme Ruby ou Python vous procureront la sécurité, mais pas la concurrence ou la vitesse. D' autre part, les langages système comme C et C ++ vous donneront de la vitesse et un peu d'accès à la concurrence, mais oublieront la sécurité, car ils vous permettront volontiers de vous tirer dans le pied par la conception. D' où vient la rouille ? Rust a été lancé en 2006 comme un projet personnel d'un employé de Mozilla nommé Graydon Hoare. Mozilla a commencé à sponsoriser Rust officiellement en 2009 et la version 1.0 a été publiée en 2015, ce qui fait Rust environ cinq ans après une période d'incubation de neuf ans. En comparaison C ++ a environ 35 ans, et C a environ quarante cinq ans. Maintenant, pourquoi Mozilla commanditerait un langage de programmation système ? Parce qu'ils en avaient marre de C ++ et voulaient un meilleur langage pour programmer Firefox. Avez-vous entendu parler de Firefox quantum en 2017 ? C' était une grosse mise à jour ! Soudain Firefox a été plus deux fois plus rapide, et moins buggy ! Pourquoi ? Parce que le noyau de Firefox a été réécrit dans Rust. Aujourd'hui, il y a environ 1,5 million de lignes de Rust dans Firefox. C'est de là que vient la rouille. Dans la section suivante, nous allons parler de la cargaison l'un des outils les plus importants de Rust. 2. Aperçu des exercices: Parlons de la façon d'arriver aux exercices pour ce cours. Ce cours de crash est livré avec un référentiel compagnon sur GitHub. C' est sous le nom d'utilisateur CleanCut. Le nom du référentiel est ultimate_rust_crash_course, séparé par des traits de soulignement. Ce référentiel contient des informations utiles sur la façon d'apprendre la rouille ainsi qu'une série d'exercices que vous pouvez exécuter. Les exercices sont contenus dans le répertoire des exercices. Vous devez cloner ou télécharger ce référentiel sur votre propre machine. Une fois que vous avez cloné le référentiel sur votre machine, chacun des exercices à l'intérieur du sous-répertoire d'exercice est un projet Rust autonome, la seule exception étant le premier exercice, qui n'est qu'un readme. Pour chacun de ces projets, vous devez ouvrir le répertoire du projet dans votre IDE ou éditeur et ouvrir le fichier src/main.rs et suivre les instructions. 3. Cargo: La cargaison est fantastique. C' est le premier outil que vous utiliserez et vous continuerez à l'utiliser pour toutes sortes de choses. Cargo est un gestionnaire de colis. Attends, un gestionnaire de paquets pour un langage de programmation système viable ? Oui. Enfin, laissez ce couler. Les programmeurs de systèmes peuvent aussi avoir de belles choses ! Vous utilisez le chargement pour rechercher, installer et gérer les packages que vous souhaitez utiliser. Cargo est aussi le système de construction. Plus de fichiers ! Cargo est également le coureur d'essai, et le générateur de documentation, etc.. C' est comme toutes les bonnes parties de NPM et Pip et bundler and make. Faisons quelque chose de réel. Si vous avez installé Rust, vous pouvez ouvrir votre terminal dès maintenant et suivre. Si vous tapez « cargo new hello » et appuyez sur Entrée, cargo créera heureux et coloré un projet nommé bonjour pour vous. Jetons un coup d'oeil à une cargaison créée pour nous : un répertoire hello avec un fichier de configuration nommé cargo.toml et un sous-répertoire source avec un fichier principal .rs. Les fichiers de configuration dans Rust utilisent le format .toml qui représente le langage minimal évident de Tom. Les fichiers source Rust utilisent l'extension .rs comme vous le voyez sur main.rs ici. Maintenant, lancez votre éditeur préféré que vous avez configuré et préparé avec le support Rust et ouvrez Cargo.Toml. Cargo.toml est le fichier de configuration de votre projet. En fait, c'est la source d'information faisant autorité sur votre projet. Nom est le nom réel de votre projet. Peu importe le nom du répertoire ou le nom de votre dépôt Git. Ce paramètre détermine le nom de votre projet. Rust utilise la gestion des versions sémantiques, ce qui signifie qu'un numéro de version est toujours trois nombres séparés par des points et que chaque nombre a une signification spécifique. Si vous n'êtes pas déjà familier avec la gestion des versions sémantiques, s'il vous plaît semver.org pour plus de détails parce que je ne vais pas y aller plus loin. Cargo va chercher un tas d'endroits probables pour trouver votre nom et adresse e-mail. S'il est capable de le trouver , il le remplira pour vous. L' un des endroits où les recherches sont effectuées est votre configuration Git globale. L'édition devrait être 2018. Si la cargaison n'a pas ajouté cette ligne pour vous automatiquement alors vous utilisez une ancienne version de Rust et vous devez la mettre à jour maintenant en exécutant « rustup update » Sauf si bien sûr c'est une année plus élevée, auquel cas vous êtes loin dans le futur et vous pouvez ignorer cela et vous devriez peut-être chercher une version mise à jour de ce cours. Plus tard, je vais vous montrer comment gérer vos dépendances ici, mais c'est tout ce que nous devons savoir sur Cargo.Toml pour le moment. Maintenant, ouvrons main.rs dans le répertoire source. Il a déjà un « Bonjour, monde ! » programme pour nous. C' est fantastique ! Quel langage utile ! Retournons à notre terminal. « cargo run » est la commande pour construire et exécuter votre projet en une seule étape. Si vous exécutez votre projet maintenant, vous verrez « Bonjour, Monde ! » juste après la cargaison finit la construction. Si vous l'exécutez à nouveau, vous verrez que le chargement peut dire que rien n'a changé dans le code source afin qu'il ne recompile pas le projet. Notez quelle cargaison est en cours d'exécution. cible/déboguer/bonjour cible/déboguer/bonjour Jetons un coup d'oeil. Il y a un répertoire cible où cargo sort tous ses artefacts de construction. C' est certainement un répertoire que vous voulez que votre logiciel de contrôle de version ignore. Nous pouvons exécuter le binaire directement si nous le voulons. Avez-vous remarqué que c'est dans un sous-répertoire de débogage ? Cargo compile votre projet avec des symboles de débogage par défaut. Ajoutez « —release » à votre commande « cargo run » pour compiler sans symboles de débogage. C' est beaucoup plus rapide de cette façon. Vous remarquerez un résultat similaire, seulement cette fois il sera stocké dans le sous-répertoire publié. La plupart du code s'exécutera beaucoup plus rapidement en mode version, mais cela prend un peu plus de temps à compiler. Je vous suggère de garder ce projet « bonjour » autour et de l'utiliser pour de petites expériences. Dans la vidéo suivante, nous allons parler de variables. 4. Variables: Parlons des variables. Ils sont assez importants. Ils ressemblent à ça. Remarquez d'abord que Rust est un langage entre crochets avec des instructions terminées par des points-virgules. La seconde rouille imite intentionnellement autant de syntaxe d'autres langages que possible, C et Python en particulier. Donc, il devrait se sentir familier dès le début pour beaucoup d'entre vous. Pour déclarer une variable, vous utilisez une instruction « let ». Ici, nous déclarons « lapins » et l'initialisons à la valeur entière de 2. Rust est un langage fortement typé, alors où est l'annotation de type ? Eh bien, chaque fois que Rust peut trouver le type approprié pour vous, vous pouvez simplement laisser l'annotation de type sur. Vous n'avez pas besoin d'écrire « auto » ou quoi que ce soit. Lorsque vous annotez le type il ressemble à ceci : un deux-points après le nom de la variable, puis le type. « i32" est un entier 32 bits signé. Nous parlerons des types primitifs dans une vidéo ultérieure. C' est ainsi que vous initialisez une seule variable. Il se trouve juste que l'instruction « let » a un truc dans sa manche qui nous permettra d'initialiser plusieurs variables à la fois. Une instruction « let » peut détruire les données sur le côté droit et l'utiliser pour initialiser des variables à l'intérieur d'un motif correspondant sur le côté gauche. Nous parlerons un peu plus sur les modèles dans la vidéo ultérieure aussi. Pour l'instant, nous allons nous contenter d'utiliser simplement un modèle de tuple quand nous voulons initialiser plusieurs variables à la fois. Les variables sont immuables par défaut dans Rust, ce qui signifie que, à moins que vous ne choisissiez de les rendre mutables vous ne pouvez jamais changer leur valeur. C' est inhabituel ! La plupart des langues sont par défaut des variables que vous pouvez modifier. Pourquoi Rust aurait-il des variables immuables par défaut ? Concurrence de sécurité et de vitesse ! Il y a beaucoup de bogues qui ne peuvent pas arriver si une valeur ne change jamais. Ainsi, la sécurité est améliorée par l'immuabilité. Les données qui ne changent jamais peuvent être partagées entre plusieurs threads sans verrous, de sorte que la concurrence est améliorée par l'immuabilité. Le compilateur peut également effectuer des optimisations supplémentaires sur les données qu'il sait ne pas changer, donc la vitesse est améliorée par l'immuabilité. C' est pourquoi les données sont immuables par défaut dans Rust : concurrence de sécurité et vitesse. Mais avons-le : parfois nous devons changer la valeur d'une variable ! Nous ne pouvons pas simplement changer la valeur des « lapins » tant qu'elle est immuable. Cela nous donnerait une erreur, cette erreur en fait ! Voici notre tout premier exemple de notre compilateur amical nous poussant dans la bonne direction ! Allons décomposer cette information. En haut nous avons un résumé de l'erreur : ne peut pas affecter deux fois à des lapins variables immuables. Ensuite, nous avons l'emplacement de l'erreur : src/main.rs, ligne 3, colonne 5. Ensuite, plus étonnamment, nous avons une explication contextuelle de ce que nous faisons mal et de la façon dont nous pourrions y remédier. Sur la ligne 2 nous initialisons un « lapins » immuable à la valeur 16. Par conséquent, sur la ligne 3 nous ne pouvons pas attribuer une valeur différente. Immuable signifie que vous ne pouvez pas le changer ! Que suggère le compilateur ? Rendez la variable de la ligne 2 mutable. Si ces informations ne sont pas suffisantes pour comprendre l'erreur, vous pouvez également obtenir une explication complète de ce type d'erreur en exécutant cette commande. Vous pouvez aller de l'avant et exécuter cette commande dès maintenant si vous le souhaitez. « rustc » est le compilateur réel que le cargo utilise sous le capot pour compiler votre code. Voici une version du code qui fonctionnera. On dirait ça. « let mut lapins = 32" OK donc nous avons des variables mutables que nous pouvons changer et des variables immuables que nous ne pouvons pas changer. Mais il y a aussi une sorte de variable qui est même immutabler : la constante. Il y a quatre choses différentes à propos de la déclaration d'une constante : d'abord, « const » au lieu de « let ». Deuxièmement, la convention est d'utiliser la casse criant pour les constantes, ce qui signifie tous les mots majuscules séparés par des traits de soulignement. Troisièmement, l'annotation de type est requise. Tu ne peux pas laisser tomber. Et quatrièmement, la valeur doit être une expression constante qui peut être déterminée au moment de la compilation. L' équipe Rust ajoute de plus en plus de fonctions de bibliothèque standard à la liste constante à chaque version. Donc, il y a en fait beaucoup de choses que vous pouvez faire au moment de la compilation qui compte comme une expression constante. Un littéral fonctionne toujours très bien. Alors pourquoi tu ferais tout ce mal pour utiliser un const ? Deux raisons. Vous pouvez d'abord placer une constante en dehors d'une fonction à la portée du module et l'utiliser où vous le souhaitez. Un global. Un mondial immuable et constant. Deuxièmement, parce que les valeurs const sont intégrées au moment de la compilation, elles sont vraiment rapides ! Dans la prochaine vidéo, nous allons parler de Scope. 5. Portée: Les variables ont une portée, qui est l'endroit dans le code où vous êtes autorisé à les utiliser. La portée d'une variable commence là où elle est créée et s'étend jusqu'à la fin du bloc. En cours de route, il est accessible à partir de blocs imbriqués. Un bloc est une collection d'instructions à l'intérieur d'accolades. Cela inclut les organes de fonction. Dans cet exemple, « x » est défini dans le bloc de la fonction principale. Ensuite, nous créons un bloc imbriqué. À l'intérieur du bloc imbriqué nous créons y, puis nous imprimons x et y, ce qui fonctionne très bien parce que x est accessible à partir de blocs imbriqués. Ensuite, le bloc se termine, et y est immédiatement abandonné à ce stade. n'y a pas de garbage collector. Les valeurs sont toujours immédiatement supprimées lorsqu'elles sortent de la portée, ce qui signifie que la dernière impression ne fonctionnera pas. Mais ne craignez pas ! Nous le découvrons au moment de la compilation ! Le message d'erreur est assez clair. Impossible de trouver la valeur y dans cette étendue. Donc, nous devrions soit hisser la deuxième macro de ligne d'impression dans la même portée que y, soit déplacer y vers le bas la même portée que la deuxième macro de ligne d'impression. Les variables peuvent également être ombragées. Une autre façon de penser à l'observation est que les variables sont toujours locales à leur portée. Ici, nous créons une variable x et l'initialisons à 5 dans le bloc externe. Ensuite, dans le bloc interne x est ombragé avec une nouvelle valeur 99. Ces deux x sont des variables différentes avec des valeurs différentes. Ils se chevauchent juste dans la portée. Le premier tirage verra X comme 99. Notez que la valeur du premier x n'est pas accessible depuis le bloc interne après avoir été ombragé. Mais dès que le bloc interne se termine, le x interne est tombé et le x externe est à nouveau accessible. Ainsi, la dernière impression voit la valeur 5. Vous pouvez également ombrage de variables dans la même portée. Ici, nous obtenons une variable mutable x avec une variable immuable x que nous initialisons à la valeur du premier x. Cela redéfinit essentiellement la variable x avec une mutabilité différente. Encore plus cool, le compilateur optimisera souvent cette opération réelle, et donc rien ne se passe réellement dans le code d'assemblage . Vous pouvez même observer une variable à un type différent dans la même étendue, ce que certaines personnes aiment faire dans pipelines de transformation de données qui rejettent les représentations intermédiaires. Donc, dans cet exemple, mème commence par les mots « plus de cloche », puis devient une image. Dans la prochaine vidéo nous passerons sur la sécurité de la mémoire. 6. Sécurité de mémoire: Rust garantit la sécurité de la mémoire lors de la compilation. Dans le cadre de cela, les variables doivent être initialisées avant pouvoir les utiliser. Ce code ne fonctionnera pas parce que l'énigme a été déclarée mais pas initialisée à une valeur avant d'essayer de l'utiliser. En fait, il ne compilera même pas ! Nous obtenons l'erreur » utilisation de éventuellement sur la variable initialisée : `énigma` ». Et si nous pouvions initialiser l'énigme en fonction d'une condition quelconque ? Le compilateur ne raisonnera pas sur la valeur d'une condition au moment de la compilation même s'il s'agit d'un vrai ou d'un faux littéral. L'évaluation conditionnelle est gérée à l'exécution, donc le compilateur ne peut pas garantir que l'énigme sera initialisée avant qu'elle ne soit utilisée car le compilateur ne sait pas quelle sera la valeur de true à l'exécution. Donc ça ne marchera toujours pas. Mais ça marche ! Le compilateur peut dire que l'énigme est garantie d'être initialisée avant qu'elle ne soit utilisée. Tant que le compilateur peut garantir que quelque chose est sûr, il vous permettra de le faire. Et si tu essayais la même chose en C ? Que faire si vous avez déclaré une variable et que vous l'avez ensuite utilisée avant de l' initialiser ? Bienvenue dans le royaume glorieux du comportement indéfini ! Sur mon Mac je l'ai essayé et puis j'ai eu le numéro 1. Je ne suis pas sûr si c'est juste ce qui s'est passé d'être en mémoire ou quoi, mais votre kilométrage peut varier car votre compilateur peut choisir de faire tout ce qu'il veut faire dans cette situation. Laissons C et sa folie tranquille. Dans la vidéo suivante nous allons passer sur les fonctions. 7. Exercice A - Variables: Il est temps de l'exercice A. L'exercice A peut être trouvé sur GitHub dans le dépôt CleanCut/Ultimate_rust_crash_course dans le sous-répertoire de l'exercice , puis dans les variables a. Pour les exercices futurs, vous devrez cloner ce référentiel et ouvrir les sous-répertoires d'exercices dans votre IDE ou éditeur, mais pour l'exercice A, nous n'avons qu'un readme car l'exercice consiste à créer un projet. Alors, ouvrez cet exercice dans votre navigateur, puis jetez-y un coup d'oeil. Je vous suggère de l'essayer et de voir si vous pouvez faire tout l'exercice, y compris le défi, par vous-même ! Si vous êtes capable de le faire très bien sans aucun problème, hésitez pas à passer à la vidéo suivante, mais si vous voulez en apprendre un peu plus, ou voir comment je l'ai fait, revenez et je vais parcourir le problème. Alors faites une pause ici et allez essayer l'exercice. Maintenant que vous avez eu la chance de passer par le projet vous-même, alors arrêtez-vous ici et allez essayer l'exercice. Maintenant que vous avez eu la chance de passer par le projet vous-même, nous allons le parcourir. Exercice a - Variables, partie 1. Faire un nouveau projet nommé « variables » en utilisant la cargaison. C'est facile ! nouvelles variables Ouvrez Cargo.Toml Je vais aller dans le projet de variables et l'ouvrir IntelliJ. Maintenant, je vais ouvrir Cargo.Toml. Rust était en effet capable de comprendre mon adresse e-mail. Changez le numéro de version à 2.3.4 juste pour le plaisir et enregistrez le fichier. Fait. Maintenant, sur src/main.rs. Déclarez les missiles variables et initialisez-la à 8 Déclarez la variable prête et initialisez-la à 2. J' utilise la dactylographie implicite ici. Expliquement, je pourrais mettre i32, ce qui serait la valeur par défaut. Ce serait la même chose. Je vais laisser ça. Changer le println ! à la fin du principal à tirer à blanc de mes missiles vierges. Prêt est le nombre et les missiles sont combien nous avons. Sauvegardez. Exécutez votre programme en utilisant la cargaison. Ok, allons faire un essai. J'ai tiré deux de mes huit missiles. Parfait. Maintenant, quels problèmes communs auriez-vous pu rencontrer ? On oublie un point-virgule. Ça ressemblerait à ça. Aide : ajouter un point-virgule ici. Un autre oublie le laissez-passer. Missile ne se trouve pas dans la portée parce que vous ne le déclarez pas. Tu essaies de l'utiliser. Alors déclarons-le. D'accord, en avant ! Partie 2. Après la première ligne d'impression, soustrayez prêt des missiles comme celui-ci. Ok, je vais juste copier et coller ça directement dans. Missiles équivaut à des missiles moins prêts. Ajouter un deuxième println ! jusqu'à la fin. Je vais copier et coller cela aussi. On peut déjà voir qu'il y a une erreur ici, mais je vais la rencontrer pour qu'on puisse l'expliquer. Alors exécutez votre programme. course de cargaison. Impossible d'assigner deux fois à des missiles variables immuables. Ok, donc les missiles sur la ligne 2 sont immuables. Allons réparer ça. Un missile mutable. Maintenant, nous devrions être en mesure de le réaffecter. clair. course de cargaison. On y va. reste six missiles. Ensuite, déclarez une constante nommée STARTING_MISSIONS et définissez-la sur 8. const STARTING_MISSIONS, crier-serpent case, le type que nous devons définir explicitement dans un const, mettons-le à 8. Déclarez une constante nommée READY_AMOUNT et définissez-la sur 2. READY_AMUNT Déclarez une constante nommée READY_AMOUNT et définissez-la sur 2. READY_AMOUNT aussi un i32 le définit sur 2. Maintenant, nous allons les utiliser à la place de nos numéros magiques. D' accord. Maintenant, nous utilisons des constantes. Ils travaillent ? Oui, ils le font. Même sortie. D' accord. Où as-tu mis les constantes ? Si vous les mettez en main, essayez de les déplacer au-dessus du principal à la portée du module. Si nous allons utiliser des constantes, nous pourrions aussi bien les rendre disponibles partout. Sauvegardez. Maintenant, nous les avons déplacés vers le haut. Ils seront disponibles à n'importe quelle fonction dans ce module. cargaison run. Pas de changement. D' accord. Maintenant que nous avons fait tous les exercices principaux, je vais vous laisser le défi. 8. Fonctions: Parlons des fonctions. Vous avez déjà vu la fonction principale un peu déjà. Parlons des fonctions en général. Les fonctions sont définies en utilisant le mot clé « fn » fn est prononcé « fun ». Le guide de style Rust dit d'utiliser la casse serpent pour les noms de fonction : mots minuscules séparés par des traits de soulignement. Une chose géniale à propos de Rust est que les fonctions n'ont pas à apparaître dans le fichier avant le code qui les appelle . Alors n'hésitez pas à laisser main en haut de votre fichier principal .rs si vous le souhaitez. Hourray pour plus de code source chronologiquement arrangé ! Les programmeurs de systèmes peuvent aussi avoir de belles choses. Les paramètres de fonction sont toujours définis par « name : type » et, bien sûr, plusieurs paramètres sont séparés par une virgule. Vous spécifiez le type de retour après les paramètres en ajoutant une flèche pointant vers le type de retour. La flèche est un trait d'union, puis un symbole supérieur à, et le corps d'une fonction se trouve à l'intérieur d'un bloc. Vous pouvez renvoyer une valeur d'une fonction en utilisant le mot-clé return comme vous le souhaitez. Il existe également un raccourci pour renvoyer des valeurs. Si vous laissez le point-virgule hors de la dernière expression dans un bloc, il sera retourné comme valeur du bloc. C' est ce qu'on appelle une « expression de queue ». En d'autres termes, c'est la même chose. Chaque fois que vous renvoyez quelque chose à la fin du bloc, la façon la plus courte est préférée dans Rust idiomatique . Appeler une fonction ressemble à la plupart des autres langues. Il n'y a actuellement aucun support pour les arguments nommés sur le site d'appel, vous devez donc fournir toutes les valeurs dans le bon ordre. Enfin, une seule fonction Rust ne prend pas en charge des nombres variables d'arguments ou des types différents pour le même argument, mais des macros telles que println le font. Un appel de macro ressemble à un appel de fonction, sauf que le nom d'une macro se termine toujours par un point d'exclamation. Nous utiliserons quelques macros communes, mais je ne vous apprendrai pas à les créer bientôt. C' est un sujet plus avancé. Dans la vidéo suivante, nous allons passer en revue le système de modules. 9. Exercice B - Fonctions: Temps pour l'exercice B - Fonctions. Allons dans le répertoire d'exercices b-functions exercices. Maintenant, ouvrons ceci dans un éditeur. Je vais utiliser dans IntelliJ. Vous pouvez utiliser VSCode ou quel que soit votre éditeur préféré. J'ai inclus des instructions pour l'exercice sous forme de commentaires à l'intérieur du fichier principal .rs, donc je vous encourage à aller essayer cet exercice, suivre les instructions et voir jusqu'où vous pouvez aller. Espérons que vous pouvez tout faire. Si vous êtes capable de tout faire, hésitez pas à sauter cette vidéo et passez à la section suivante. Si vous avez des ennuis ou si vous voulez voir comment je l'ai fait, alors revenez à cet endroit dans la vidéo et nous allons passer en revue. Bon, maintenant que tu as eu la chance de faire l'exercice toi-même, passons-y. Numéro un. Essayez d'exécuter ce code avec cargo run et jetez un oeil à l'erreur. Je peux le faire ! course de cargaison. Ok, impossible de trouver la zone de valeur dans cette étendue. Donc nous avons un problème de portée ici sur la ligne 15. Allons jeter un oeil. Ligne 15, zone est zone. Ok, la zone est notre variable. Oh regarde, c'est défini dans cette portée interne, il est laissé tomber ici, il n'est pas disponible. Dans ce cas nous allons simplement nous débarrasser de cette portée intérieure inutile, sauvegardez cela. Maintenant, si nous dégageons et que la cargaison court, la zone est 0. Très bien, deux : La zone qui a été calculée n'est pas correcte. Allez réparer la fonction area_of ci-dessous, puis exécutez à nouveau le code et assurez-vous que cela a fonctionné. Vous devriez avoir une superficie de 28. D' accord. Alors, allons trouver le code. area_of, 2a. Correction de cette fonction pour calculer correctement la zone d'un rectangle donné dimensions x et y en multipliant x et y retournant le résultat. D' accord. Donc, nous avons une fonction area_of, il faut un x, qui est un entier, un y, qui est un entier, et retourne un entier qui représente la zone. il prend un x, qui est un entier, un y, qui est un entier, et retourne un entier qui représente la zone. Assez facile ! Va juste x fois y ici, on devrait être tous bons pour y aller. La zone est de 28. Super. Défi : La ligne précédente n'est pas idiomatique. Exécutez la cargaison clippy. Trouve ce qui ne va pas et répare ça. Ok, je vais te laisser ce défi ! Je vous encourage à le faire, car ce n'est pas idiomatique. Nous n'utilisons pas d'expression de queue, alors allez réparer ça ! Numéro 3 : décommenter la ligne ci-dessous. Cela ne fonctionne pas encore, car la fonction de volume n'existe pas. Créez la fonction de volume. Il devrait prendre trois arguments, multiplier les trois arguments ensemble, et retourner le résultat qui devrait être deux cent quatre-vingts. D' accord. Si nous sommes coincés, rappelez-vous que c'est très similaire à area_of. Ok, je ne pense pas qu'on en aura besoin, mais allons-y et décommentons ça. Aller volume de fonction. Si je l'épelle correctement, cela aiderait. D' accord. Il a besoin d'une largeur, d'une hauteur et d'une profondeur donc nous dirons x est un i32 et Y est un i32 et z est un i32. Qu' est-ce qu'il retourne ? Un i32. Une vraie diversité de types ici. Ok, x fois y fois z. nous allons laisser ça comme expression de queue, et la cargaison court. Le volume est 280. Super ! C' est tout pour cet exercice. 10. Système de module: Rust dispose d'un système de modules puissant et flexible. Prenons ce projet « hello » comme exemple. Ajoutons un module de bibliothèque racine en créant un fichier lib.rs. Nous savons déjà que main.rs est un fichier spécial qui sera le binaire hello. lib.rs est un fichier spécial qui sera la racine de la bibliothèque hello. Ouvrons lib.rs et mettons une fonction à l'intérieur. Voici une fonction assez simple. C' est le seul contenu de la bibliothèque. Allons diviser l'écran et regarder main.rs. La fonction principale tente d'appeler directement la fonction de bienvenue de la bibliothèque en spécifiant le chemin absolu de la fonction. Le chemin absolu est le nom de la bibliothèque, qui est le même que le nom de votre projet dans Cargo.Toml, Le chemin absolu est le nom de la bibliothèque, qui est le même que le nom de votre projet dans Cargo.Toml, Bonjour, dans ce cas, puis l'opérateur de portée, qui est deux deux-points, puis le nom de la fonction : saluer. Cela fonctionne presque, mais tous les éléments d'une bibliothèque sont privés par défaut, même aux binaires dans le même projet. Ajoutons pub devant la fonction de salutation pour le rendre public. Maintenant, le code fonctionne, mais spécifier le chemin absolu de quelque chose à chaque site d'appel pourrait être vraiment douloureux, surtout si le chemin est vraiment long, ce qui est l'endroit où l'instruction use entre. L' utilisation apporte un élément d'un chemin dans une certaine portée et je l'appellerai souvent import sans y penser parce que je suis tellement habitué à Python. Revenons donc à notre exemple et ajoutons une instruction use au-dessus de la fonction principale. Puisque l'instruction use est en dehors de toute portée plus petite, cela apporte la salutation dans la portée de tous les principaux. Maintenant, nous pouvons simplifier l'appel de fonction pour simplement saluer. Cela fonctionne exactement de la même manière qu'avant, mais c'est plus concis sur le site appelant. Cette instruction use est la façon dont vous apporterez dans la portée tout ce qui provient de la bibliothèque standard ou de tout autre projet que vous souhaitez utiliser. La bibliothèque standard est toujours disponible par défaut. C' est écrit « s t d », mais quand vous le lisez à haute voix, vous dites « standard ». Donc, c'est « carte de hachage des collections standard ». Vous deviendrez vraiment familier avec le contenu de la bibliothèque standard, mais jusqu'à ce que vous me permettiez de vous montrer un raccourci pour trouver de la documentation pour les choses dans la bibliothèque standard. Allez sur Google et tapez « rouille std », puis la chose que vous voulez trouver. Par exemple, si je voulais savoir sur le type de vecteur, je mettrais « rust std vector ». Vous trouverez que le résultat principal est presque toujours la page de l'élément de bibliothèque standard dont vous avez besoin. C' est plutôt cool. Je l'utilise tout le temps encore. Mais que faire si vous avez besoin de quelque chose qui n'est pas dans la bibliothèque standard ? crates.io est le registre des paquets de Rust. C' est là que je commencerais. Pour les besoins d'aujourd'hui, vous pouvez considérer caisse comme un synonyme de paquet. La plupart des membres de la communauté Rust utiliseront des caisses et des paquets de façon interchangeable. Avec cela à l'esprit, le style de caisse nommé est logique pour un registre de paquets, mais puisque caisse a techniquement plusieurs significations, et je veux être précis dans mon enseignement, je dirai surtout paquet. Une fois que vous avez identifié le nom et la version du paquet que vous souhaitez utiliser, vous devez revenir à Cargo.toml et ajouter le paquet en tant que dépendance. Nous allons le faire dans la section de dépendance que nous avons ignorée plus tôt. Le format est le nom du paquet, puis un signe égal, puis la version du paquet entre guillemets. Ici, j'ai spécifié le paquet rand à utiliser pour générer des nombres aléatoires. Maintenant que je l'ai répertorié dans mes dépendances, je peux l'utiliser à la fois de ma bibliothèque et mon binaire soit par un chemin absolu ou en amenant un élément spécifique dans la portée avec une instruction use. Dans la vidéo suivante nous passerons sur les types scalaires. 11. Types scalaires: Il existe quatre types scalaires. Entiers, flottants, booléens et caractères. Allons d'abord sur les types entiers. Il y en a beaucoup. Les entiers non signés commencent par u, suivi du nombre de bits de l'entier, cohérents sur toutes les plates-formes, à l'exception de usize. usize est la taille du type de pointeur de la plate-forme et peut représenter toutes les adresses de mémoire du processus. C' est aussi le type que vous utiliserez habituellement pour indexer dans un tableau ou un vecteur. Les entiers signés sont exactement la même histoire, sauf qu'ils utilisent i pour leur préfixe. i pour entier, je suppose. isize a également le même nombre de bits que le type de pointeur de plates-formes. La valeur isize maximale est la limite supérieure de la taille de l'objet et du tableau. Cela garantit que isize peut être utilisé pour calculer les différences entre les pointeurs et être capable d'adresser chaque octet dans une valeur comme une structure. Si vous n'annotez pas un littéral entier, il est par défaut i32 car c'est généralement l'entier le plus rapide, même sur les architectures 64 bits. Maintenant, ce n'est pas parce que les types ont le même nombre de bits sur toutes les architectures que tous les types sont pris en charge sur toutes les architectures. Un microcontrôleur 16 bits peut uniquement prendre en charge ces types. Les littéraux entiers peuvent être spécifiés de plusieurs façons. Décimal est comme vous vous attendiez. L'hexadécimal commence par zéro x. octal commence par zéro o. binaire commence par zéro b, et un seul octet u8 peut éventuellement être spécifié par des guillemets simples b- contenant un caractère UTF-8 dans la plage ASCII. C' est assez rare à utiliser dans mon expérience. La plupart des gens utilisent simplement un nombre entier décimal compris entre 0 et 255. Les termes u8 et octet sont utilisés de façon interchangeable dans Rust. Vous entendrez tout le temps octet au lieu de u8. Les représentations qui prennent plus d'un chiffre peuvent avoir n'importe quel nombre de traits de soulignement ignorés à l'intérieur ou à la fin d'eux. C' est juste pour la commodité et la lisibilité. Par exemple, ce sont les mêmes nombres avec quelques traits de soulignement dans des endroits que nous aimons habituellement, mais vous pouvez aussi le faire si vous le souhaitez. Le point est que les traits de soulignement sont ignorés. Les types à virgule flottante sont beaucoup plus simples. f32 a 32 bits de précision et f64 a 64 bits de précision. f64 est la valeur par défaut car il a plus de précision, mais il peut être vraiment lent sur les architectures de moins de 64 bits, alors soyez prudent avec cela. Les littéraux à virgule flottante suivent la norme IEEE-754 mais ressemblent essentiellement à ceci. Aucun suffixe spécial n'est requis, mais vous devez toujours avoir au moins un chiffre avant le point. Donc, ce n'est pas un littéral à virgule flottante valide , mais c'est parce qu'il a un chiffre devant le point. Les littéraux numériques peuvent éventuellement inclure le type en tant que suffixe. Habituellement, lorsque vous voulez un type spécifique, vous annoterez une déclaration de variable et le type du littéral sera déduit, mais il est également complètement correct de suffifier le littéral avec le type que vous voulez. Ceci est particulièrement utile si vous voulez passer un littéral à une fonction générique qui pourrait accepter plusieurs types numériques. C' est une situation où les traits de soulignement peuvent vraiment améliorer la lisibilité. Maintenant pour le type booléen. Le type réel est spécifié avec bool qui est assez facile. Les deux libéraux booléens sont vrais et faux, tous en minuscules. Les booléens ne sont pas des entiers, alors n'essayez pas d'utiliser l' arithmétique sur eux, cela ne fonctionnera pas, sauf si vous les cassez en un type entier comme celui-ci. Le type de caractère est mal nommé. Même si vous le spécifiez avec soin, il représente en fait une seule valeur scalaire unicode qui pourrait être quelque chose d'un caractère de notre alphabet à un caractère de l'alphabet de quelqu'un d'autre à un idéographe ou un diacritique ou un emoji ou un non-imprimable Caractère de contrôle Unicode qui pourrait représenter n'importe quoi d'un son à une action. Un caractère est toujours de 4 octets, ce qui fait effectivement un tableau de caractères une chaîne USC-4 ou UTF-32. littéraux de caractère sont spécifiés à l'aide de guillemets simples, et surtout, les caractères sont assez inutiles. Les chaînes sont UTF 8 et les caractères ne le sont pas, donc les chaînes n'utilisent pas de caractères en interne. Les fichiers source sont également UTF-8, donc les chances sont que lorsque vous voulez traiter un seul caractère, ce sera une chaîne UTF-8 pas un littéral de caractère. Nous parlerons de cordes dans une vidéo plus tard. Dans la vidéo suivante, nous allons parler des types composés. 12. Types de composés: Les types composés rassemblent plusieurs valeurs d'autres types en un seul type. Le premier type de composé est le tuple. Les tuples stockent plusieurs valeurs de n'importe quel type. On l'a déjà vu. Il ressemble à ceci : parenthèses contenant des valeurs séparées par des virgules. L'annotation de type est assez intuitive : parenthèses entourant les types séparés par des virgules. Il existe deux façons d'accéder aux membres d'un tuple. La première façon est d'utiliser la syntaxe point, également appelée expression d'accès au champ. Je crois que Rust utilise la syntaxe des points ici au lieu de crochets pour souligner que les membres des tuples ne sont pas toujours du même type. Puisque les champs d'un tuple n'ont pas de noms, vous utilisez leurs index, en commençant par 0. La deuxième façon d'accéder aux membres d'un tuple est tout à la fois. On l'a déjà vu. Vous pouvez utiliser un motif pour détruire et accéder à tous les éléments d'un tuple. Sachez que les tuples ont actuellement une arité maximale de douze au-dessus de laquelle vous pouvez techniquement toujours utiliser le tuple, mais seulement avec des fonctionnalités limitées. Atrition signifie combien d'éléments sont dans le tuple. Donc, ce type de tuple a une arité de 4. Puisque l'arité maximale actuelle est de douze, vous pouvez avoir une douzaine d'éléments dans un tuple, mais pas une douzaine de boulangers. Du moins pas avec toutes les fonctionnalités. Les tableaux par contraste, stockent plusieurs valeurs du même type. Vous pouvez les spécifier littéralement avec des crochets et des virgules ou avec une valeur et combien vous voulez séparer par un point-virgule. Notez que l'annotation de type pour un tableau utilise toujours la forme point-virgule. Même lorsque vous spécifiez toutes les valeurs littérales dans le tableau. Vous indexez des valeurs dans un tableau comme vous l' attendez avec des crochets. Ce que vous ne vous attendez pas cependant, c'est que les tableaux sont limités à une taille de 32, au-dessus de laquelle ils perdent la plupart de leurs fonctionnalités. Les tableaux vivent sur la pile par défaut et sont de taille fixe, de sorte que vous utiliserez généralement des vecteurs ou des tranches de vecteurs au lieu de tableaux. Nous parlerons des vecteurs dans une vidéo ultérieure. Dans la vidéo suivante nous allons parler de flux de contrôle. 13. Exercice C - Types simples: Temps pour l'exercice C. Temps pour l'exercice C. Nous allons passer en revue les types simples. Je vous encourage à aller de l'avant et à faire ce projet maintenant. Une fois que vous avez terminé, revenez et nous allons continuer et passer en revue les réponses. Maintenant que vous avez eu la chance de faire ces exercices vous-même, passons à travers. Numéro 1 : Passez des parties de coords à la fonction de différence d'impression. Cela devrait montrer la différence entre les deux numéros dans les coords lorsque vous faites des cargaisons. Utilisez l'indexation de tuple. La fonction print_difference est définie sous la fonction principale. Cela peut vous aider si vous regardez comment il est défini. D' accord. Décommentons cette ligne. Décommentons cette ligne. print_difference. Si nous faisons défiler vers le bas, nous voyons qu'il faut un x et un y, qui sont f32s. Reviens au sommet. Coords est un tuple de deux f32s. Nous avons donc simplement besoin d'extraire ces deux et de les passer à la fonction individuellement. Nous allons donc utiliser notre indexation de tuple. Donc, coords.0, coords.1. Sauvegarde. Cargo run. Super ! La différence entre 6,3 et 15 est de 8,7. Numéro deux - nous voulons utiliser la fonction print_array pour imprimer des coords, mais coords n'est pas un tableau. Créez un tableau de type [f32 ; 2] et initialisez-le pour contenir les informations des coords. Décommentez la ligne print_array et exécutez le code. Encore une fois, nous avons du code à décommenter. Voici notre nouveau tableau. Faisons un tableau littéral, et nous allons le mettre à nouveau, une fois de plus, coords.0 coords.1. Ça devrait marcher très bien. Si vous vouliez spécifier explicitement le type, nous dirons Quel est le type ? f32. Un point-virgule. Combien en veut-on ? Deux, donc deux f32. clair. course de cargaison. Maintenant, j'ai : Les coordonnées sont (6.3, 15) La fonction a pris cette valeur très bien. Suivant ! Numéro trois. Nous avons cette série ici, qui est un tableau de i32. Rendre la fonction ding heureuse en lui passant la valeur 13, c'est juste là, hors du tableau de série utiliser l'indexation de tableau. Fait correctement, la course de chargement produira la sortie supplémentaire : Ding, vous avez trouvé 13 ! Alors faisons ça. Donc, la série est notre tableau. Nous allons utiliser nos crochets pour indexer dans le tableau, et donc nous allons obtenir zéro, un, deux, trois, quatre, cinq, six ! Mettez six, économisez. Effacer et courir. Ok ! Ding, tu as trouvé 13 ! Numéro quatre : Passez la fonction on_off la valeur true de la variable mess. Oh, donc on a ce bordel ici et voilà le vrai. Effectué correctement la course de chargement produira la sortie supplémentaire : Les lumières sont allumées ! Je vais vous faire commencer. D' accord. Alors voici l'appel on_off. Il y a du désordre, qui est un tuple. Donc on a un « .2". Donc voici zéro, un, deux. Donc deux est ce tableau. Utilisons l'indexation de tableau pour obtenir zéro, un. Ça nous amènera ce tuple ici. Donc indexation de tableau, un. Maintenant, on en est à ça. Maintenant, on a encore un tuple. Nous allons utiliser l'indexation de tuple et en extraire l'élément zéro. Donc c'est un .0, point-virgule. Sauvegardez. Viens essayer Ok. Les lumières sont allumées. D' accord. Cinq. Quel gâchis — les fonctions dans un binaire ! Organisons-nous ! Créez un fichier de bibliothèque. Pour que src/lib.rs Déplacez toutes les fonctions dans la bibliothèque. Rendez-les tous publics et mettez toutes les fonctions dans la portée en utilisant des instructions d'utilisation. D' accord. Rappelez-vous que le nom de la bibliothèque est défini dans Cargo.Toml Vous devez savoir que pour l'utiliser. D' accord. Donc, ce n'est pas parce que c'est dans le répertoire c-simple-types que cela ne signifie pas nécessairement que c'est le nom de notre projet. Je veux dire que ça pourrait l'être ! Mais allons jeter un oeil. Donc si j'ouvre ma barre latérale ici et aller à Cargo.Toml — Ah ! ding_machine est le nom de notre projet. Revenons à la principale. Faisons notre lib.rs suivant. Donc, à l'intérieur de src, nous allons nouveau fichier de rouille. lib.rs. Maintenant que nous avons notre fichier de bibliothèque, allons y mettre nos fonctions. Alors, revenez à la main. Prenez toutes nos fonctions. Couper. Va à la bibliothèque. Coller. Maintenant, ils sont tous privés par défaut, donc nous ne pouvons pas encore les utiliser, alors ajoutons pub à toutes ces fonctions. D' accord. Maintenant, ils sont tous publics. Retournons au principal. Fermons la barre latérale. Nous devons tous les mettre dans la portée. Donc, utilisez, le nom de notre projet et donc le nom de notre bibliothèque est ding_machine. Notre opérateur de portée, et voici nos différentes choses que nous pourrions utiliser. Donc oui, nous voulons on_off, et ensuite faisons-les tous ici. Nous voulons on_off, nous voulons print_distance. Nous voulons print_array. Nous voulons print_difference. On en a raté un ? Ah, Ding. On veut Ding. Voyons si on les a tous maintenant. D'accord. nous les avons tous maintenant. D'accord. On dirait qu'on les a tous. Alors allons courir ce très vite et assurez-vous que cela fonctionne toujours ! Libre, cargaison. Ah, un avertissement. Qu' est-ce que c'est ? Ok, import inutilisé : print_distance import inused : print_distance print_distance est utilisé par le défi, donc je vais vous laisser le soin d'aller l'utiliser et de me débarrasser de cet avertissement. 14. Flux de contrôle: Le flux de contrôle est génial dans Rust. « si » les expressions sont géniales. Vous n'avez pas besoin de parenthèses autour de la condition, car tout entre le « if » et l' accolade d'ouverture est la condition. La condition doit être évaluée à un booléen, car Rust n'aime pas la coercition de type et la plupart du temps ne force pas les types. Si vous voulez enchaîner une condition, vous utilisez « else » et « if » séparément, et bien sûr vous pouvez finir avec « else ». « if » est une expression, pas une déclaration. Les instructions ne retournent pas de valeurs, les expressions le font, ce qui signifie que nous pouvons changer ce code pour ceci : message reçoit la valeur de l'expression « if ». Quatre choses à noter : Numéro un, il n'y a pas de points-virgules après les valeurs de branche pour le rendre de sorte que les valeurs soient renvoyées des blocs sous forme d'expressions de queue. Deux : nous ne pouvons pas utiliser return à cette fin, même si nous le voulions, car return ne s'applique qu'aux blocs qui sont des corps de fonction, donc return reviendrait hors de la fonction actuelle. Trois : tous les blocs retournent le même type. La rouille est fortement typée, et Quatre : il y a un point-virgule à la fin de l'expression if. Si vous n'utilisez pas la valeur d'une expression if, Rust vous permettra de tricher et de laisser le point-virgule, mais si vous utilisez la valeur d'une expression if dans une instruction, alors vous devez placer un point-virgule après avant de commencer toute autre instruction dans la bloc. Les accolades ne sont pas facultatives, en passant. Vous devez toujours les avoir, ce qui signifie que vous ne frappez jamais cette terrible situation que vous faites en C où vous voulez ajouter une deuxième ligne à la branche d'une instruction if, mais à la place votre deuxième ligne finit en dehors du corps de la branche et se produit inconditionnellement malgré votre indentation minutieuse. Ce n'est pas le pire ? Un autre point douloureux que nous évitons est l'opérateur ternaire confus en C. Oui, c'est court et concis, mais il est difficile de lire même pour une expression simple comme celle-ci, et peut devenir carrément laid si vous l'imbriquez. Je veux dire, ce n'est pas clair pour moi ce qui se passe ici sans analyse mentale minutieuse. Même bien formaté il faut de l'expérience pour comprendre ce qui se passe. Dans Rust, puisque si est une expression, vous pouvez simplement l'écrire comme ceci. C' est assez évident ce qui se passe juste en le lisant et la version imbriquée est toujours lisible. Passons à la boucle inconditionnelle. Il s'avère que si le compilateur sait qu'une boucle est inconditionnelle, il y a des optimisations assez cool qu'il peut faire. Bien sûr, même les boucles inconditionnelles doivent finir par finir, donc vous utilisez l'instruction break pour cela, mais attendez qu'il y a plus ! Que faire si vous voulez sortir d'une boucle imbriquée ? Je peux le faire ! Tout d'abord, annotez la boucle dont vous voulez sortir avec une étiquette. Celui-ci s'appelle Bob. Les étiquettes ont une syntaxe étrange : une apostrophe unique commençant un identificateur. Certaines personnes disent un identifiant de tique. Ensuite, dites à la rupture de quelle boucle vous voulez sortir. Lorsque ce code frappe l'instruction break dans la boucle interne, il se cassera tout le chemin hors de la boucle la plus externe. Continuer est très similaire. En soi , il continue la boucle la plus intérieure. Si vous lui donnez une étiquette, il continue la boucle nommée. Les boucles « while » ont tous les mêmes comportements que les boucles inconditionnelles, Si vous lui donnez une étiquette, alors elle continue la boucle nommée. Les boucles « while » ont tous les mêmes comportements que les boucles inconditionnelles, sauf qu'elles mettent également fin à la boucle lorsque leur condition est évaluée à false. Autrement dit, la valeur booléenne exacte de false. Rappelez-vous, Rust refuse de forcer les expressions aux booléens. Vous savez, si vous y pensez alors que les boucles sont à peu près juste du sucre syntaxique pour mettre une condition de rupture annulée au sommet d'une boucle inconditionnelle. n'y a pas de construction « do while » dans Rust, mais il est assez facile de réorganiser ce code pour que cela se produise. Il suffit de déplacer la condition vers le bas de la boucle. Voila ! « faire tout le temps. » Similaire aux langages de script modernes, la boucle « for » de Rust itère sur n'importe quelle valeur itérable. Les types composés et de collection auront généralement quelques façons différentes d'obtenir une valeur itérable pour eux. La méthode iter est l'une des façons les plus courantes d'obtenir un itérateur. L'itérateur que vous utilisez détermine quels éléments sont retournés et l'ordre dans lequel ils sont retournés. iter itère tous les éléments d'une collection dans l'ordre si la collection est ordonnée, et au hasard si la collection n'est pas ordonnée. Si vous êtes un fan du style de programmation fonctionnel, vous serez heureux d'apprendre que vous pouvez empiler des méthodes telles que la carte, le filtre et le pliage et ils seront évalués paresseusement. Comme un bonus supplémentaire, la boucle for peut prendre un motif pour détruire les éléments qu'elle reçoit et lier les parties internes à des variables, tout comme l'instruction let. Seulement dans ce cas, les variables sont locales au corps de la boucle for. Les programmeurs de systèmes peuvent aussi avoir de belles choses ! Seulement dans ce cas, les variables sont locales au corps de la boucle for. Les programmeurs de systèmes peuvent aussi avoir de belles choses ! Vous voudrez probablement utiliser des plages avec pour les boucles à un moment donné. La syntaxe d'une plage est de deux points séparant vos points de départ et de fin. Le départ est inclusif et la fin est exclusive. Donc, cela comptera de zéro à quarante neuf, et si vous utilisez point égal, la fin sera aussi inclusive . Donc, cela comptera de zéro à 50. Dans la vidéo suivante je vais parler de cordes. 15. Strings: cordes. Je vais vous avertir devant : voici des dragons ! Je ferai de mon mieux pour vous diriger correctement. Il existe au moins six types de chaînes dans la bibliothèque standard Rust. Mais nous nous soucions surtout de deux d'entre eux qui se chevauchent. Le premier est appelé une tranche de chaîne et vous le verrez presque toujours comme une tranche de chaîne empruntée. On parlera plus d'emprunt plus tard. Une chaîne littérale est toujours une tranche de chaîne empruntée. Une tranche de chaîne empruntée est souvent appelée une chaîne qui peut être vraiment déroutante lorsque vous apprenez que l' autre type de chaîne est une chaîne avec une majuscule S. La plus grande différence entre les deux est que les données d'une tranche de chaîne empruntée ne peuvent pas être alors que les données d'une chaîne peuvent être modifiées. Vous allez souvent créer une chaîne en appelant la méthode to_string () sur une tranche de chaîne empruntée ou en passant une tranche de chaîne empruntée à String። from. Une tranche de chaîne empruntée est constituée en interne d'un pointeur vers certains octets et une longueur. Une chaîne est composée d'un pointeur sur certains octets, d'une longueur et d'une capacité qui peut être supérieure à ce qui est actuellement utilisé. En d'autres termes, une tranche de chaîne empruntée est un sous-ensemble d'une chaîne de plusieurs façons. C' est pourquoi ils partagent un tas d'autres caractéristiques. Par exemple, les deux types de chaînes sont valides UTF-8 par définition, par application du compilateur et par vérification de l'exécution. De plus, les chaînes ne peuvent pas être indexées par position de caractère. Pourquoi pas ? Parce que l'anglais n'est pas la seule langue au monde ! En fait, Google m'a dit qu'il y avait plus de 6900 langues vivantes, et emojis en plus de cela, et ils semblent tous faire leur chemin dans Unicode, et les chaînes sont Unicode, ce qui signifie que les choses deviennent compliquées. Jetons un coup d'oeil au mot thaï sawatdee. Disons que nous voulions obtenir ce truc à ce que nous pensons être l'indice 3. En fin de compte, cette chaîne est stockée comme un vecteur de 18 octets. Pourrions-nous obtenir ce que nous voulions si nous indexions par octets ? Même pas près ! Les scalaires Unicode en UTF-8 peuvent être représentés par 1, 2, 3 ou 4 octets, et vous devez parcourir octets dans l'ordre, pour dire où un scalaire se termine et le suivant commence. Dans ce cas, tous les trois octets sont un scalaire Unicode, donc s'il y avait un moyen d'indexer dans les scalaires aurons-nous ce que nous voulons ? Plus près, mais toujours loin. Les diacritiques sont des scalaires Unicode qui se combinent avec d'autres scalaires Unicode pour produire un graphème différent, et le graphème est généralement ce qui nous intéresse. Donc maintenant, vous comprenez que les graphèmes se décomposent en quantités variables de scalaires, qui se décomposent en quantités variables d'octets. Dans le cadre de l'accent mis par Rust sur la vitesse, les opérations d'indexation sur les collections de bibliothèques standard sont toujours garanties comme des opérations à temps constant. Vous ne pouvez pas le faire avec des chaînes, car les octets, qui sont indexables, ne sont pas garantis d'être ce que les gens veulent quand ils indexent dans une chaîne, et les graphèmes, que les gens veulent, ne peuvent être récupérés qu'après avoir examiné lentement une séquence de octets. Donc, lorsqu'il est présenté avec une chaîne, vous avez quelques options : vous pouvez utiliser la méthode bytes () pour accéder au vecteur d'octets UTF-8 dans lequel vous pouvez indexer si vous le souhaitez puisque les octets sont de taille fixe. Cela fonctionne très bien pour le texte anglais simple tant que vous vous en tenez à la partie qui chevauche ASCII. Vous pouvez utiliser la méthode chars () pour récupérer l'itérateur que vous pouvez utiliser pour itérer à travers les scalaires Unicode , et enfin vous pouvez utiliser un paquet comme unicode-segmentation qui fournit des fonctions pratiques qui retournent des itérateurs qui gèrent les graphèmes de divers types. Avec chacune de ces approches, vous savez que si vous pouvez indexer dans quelque chose, ce sera une opération rapide et constante tandis que si vous itérisez quelque chose, il va traiter un nombre variable d'octets pendant chaque itération de la boucle. Espérons que vous pouvez contourner la plupart de ces problèmes en utilisant l'une des nombreuses méthodes d'aide créées pour manipuler les chaînes, mais si vous finissez par utiliser manuellement l'un des itérateurs, les itérateurs ont une méthode pratique appelée nth () que vous pouvez utiliser à la place de et maintenant vous savez pourquoi vous devez choisir un itérateur et utiliser nth () au lieu de pouvoir indexer directement dans une chaîne . Dans la prochaine vidéo nous allons parler de la propriété. 16. Exercice D - Flux de contrôle et d'ensembles: Il est temps de l'exercice D - Contrôle du flux et des chaînes. abord, vous devriez aller essayer ces exercices par vous-même et voir si vous pouvez les traverser. abord, vous devriez aller essayer ces exercices par vous-même et voir si vous pouvez les traverser. Si vous ne parvenez pas à les traverser ou si vous voulez voir mon point de vue sur eux, revenez à cet endroit dans la vidéo et nous allons les revoir. Maintenant que tu as eu la chance d'essayer ça toi-même, passons en revue ces exercices. D' accord. Donc, d'abord, nous collectons nos arguments dans un vecteur de chaînes, puis nous allons consommer le vecteur d' arguments et itérer à travers chacune de nos chaînes. D' accord. 1a - votre tâche : gérer les arguments de ligne de commande. Si arg est sum, appelez la fonction sum. Si arg est double , appelez la fonction double. Si arg est autre chose, appelez la fonction count en lui passant arg. Astuce : Vous devrez convertir vos littéraux de chaîne en une chaîne avant de pouvoir les comparer avec doubles égaux. D' accord. Si arg est sum, mais converti en une chaîne, appelez la fonction sum. Sinon, si arg est égal à, cette fois faisons String። from (« double ») Ils font exactement la même chose. C' est deux façons différentes de le faire. Je fais généralement le to_string (). Alors, appelez double. Sinon, pour toute autre condition, nous appellerons count et le passerons arg. Bon, allons essayer. Ça ne fait rien parce qu'on ne lui a pas donné d'argument. Comment donnez-vous un argument à votre programme ? Vraiment facile. Donne-lui juste un argument. Maintenant, vous pouvez entrer en collision avec un argument réel de la cargaison. Pour le rendre explicite vous pouvez ajouter un double tiret, et maintenant les arguments après il ne seront pas interprétés par le fret. La somme est zéro. Vous pouvez doubler x 0 fois avant qu'il ne soit plus grand que 500, et tout le reste est vide en ce moment. On réparera ça dans une minute. 1b - Essaie de passer somme, double, et bananes... OK on a fait ça. Ensuite, 2 - utilisez une boucle for pour parcourir des entiers de 7 à 23 inclusivement en utilisant une plage et ajoutez-les tous ensemble. Incrémenter la variable de somme. Astuce : Vous devriez obtenir 255. Exécutez-le avec la somme de course de fret. D' accord. Donc, utilisez une boucle for. Donc, pour... quelle est notre variable d'index ? Allons-y, maintenant, quelle est notre gamme ? Eh bien, nous devons passer de 7 à 23 inclusivement, donc nous pouvons aller 7.. 24, c'est une gamme exclusive, donc ça finira par 23. Ou on pourrait aller.. =23 pour utiliser une gamme inclusive. Faisons ça dans ce cas, et on dira... qu'est-ce qu'on fait ? Eh bien, nous prenons notre somme mutable et nous y ajouterons, et ça devrait être tout. Cela devrait passer en boucle, il se terminera à la fin de la plage et ensuite nous allons imprimer la somme est, espérons-le, 255. Allons faire un essai. Somme de cargaison. Sum, c'est 255, génial ! 3 - utilisez une boucle while pour compter combien de fois vous pouvez doubler la valeur de x... multiplier x par deux... avant qu'elle ne soit supérieure à 500. Nombre d'incréments à chaque fois à travers la boucle. Exécutez-le avec le nombre de cargaisons. Astuce : la réponse est 9 fois. Ok, alors pendant la boucle... quelle est notre condition ? Eh bien, avant qu'il soit plus grand que 500, donc x est plus petit que 500. D'accord. Qu' est-ce qu'on fait ? Nombre d'incréments plus égal à 1, puis nous avons un double x , donc x fois égale à deux. Maintenant, nous pouvons aussi faire x = x * 2. On utilise juste le sucre syntaxique ici pour l'opération combinée. Va essayer, nous allons effacer l'écran, somme de course de fret. La somme est... Désolé, celui-là est double. Vous pouvez doubler x 9 fois avant qu'il ne soit plus grand que 500. Parfait ! Et puis nous sommes sur le défi. Ok, je vais te laisser faire ça. C' est le défi ! 17. Propriétaire: Parlons de la propriété. C' est en fait la propriété que j'ai choisi d'apprendre Rust en premier lieu. a quelques années, j'ai décidé que je voulais vraiment maîtriser un langage de programmation système. Je ne sais pas pour toi, mais je n'ai pas beaucoup de temps dans ma journée. Donc, si je devais maîtriser un langage de programmation système, je voulais m'assurer que cela en valait la peine. J' ai passé un mois à faire des recherches sur toutes les options et cela s'est vraiment réparti à trois choix viables à la fin. C ou C ++ avec lesquels j'avais déjà eu des expériences douloureuses, ou un nouveau langage prometteur appelé Rust. Le modèle de propriété de Rust est la raison pour laquelle j'ai choisi de passer mon temps supplémentaire à me concentrer sur Rust. La propriété est ce qui rend ces garanties de sécurité folles possibles et rend Rust si différent des autres langages de programmation des systèmes. La propriété est ce qui rend tous ces messages d'erreur informatifs du compilateur possibles et nécessaires. Il y a trois règles à la propriété : Tout d'abord, chaque valeur a un propriétaire. n'y a pas de valeur en mémoire, aucune donnée qui n'a pas de variable qui la possède. Deuxièmement, il n'y a qu'un seul propriétaire d'une valeur. Aucune variable ne peut partager la propriété. D' autres variables peuvent emprunter la valeur dont nous parlerons en une minute, mais une seule variable la possède . Troisièmement lorsque le propriétaire sort de la portée, la valeur est immédiatement supprimée. Voyons la propriété en action. Créons une chaîne s1, puis créons une autre variable s2 et lui assignons la valeur de s1. Ce qui arrive à cette chaîne n'est pas une copie. À ce stade, la valeur de S1 est déplacée vers S2 car une seule variable peut posséder la valeur. Si nous essayons d'aller de l'avant et d'utiliser s1 après ce point, nous obtenons une erreur de compilateur : emprunter de la valeur déplacée s1. que se passe-t-il ici ? Eh bien, nous avons des sections de pile et de tas de mémoire ; juste un rafraîchissement super rapide : La pile stocke les valeurs dans l'ordre, ce qu'elle peut faire parce que les valeurs sont toujours de taille fixe, et la dernière valeur dans est la première valeur, ce qui a tous tendance à être très rapide parce que c'est si compact et prévisible. tas, en revanche, stocke des valeurs partout, et les valeurs ont tendance à être toutes sortes de tailles, et ne sont pas abandonnées dans un ordre correspondant au moment où elles ont été créées, ce qui a tendance à rendre l'utilisation du tas plus lente que cette pile parce que vous êtes toujours sauter autour de la mémoire, vider et recharger le cache mémoire de votre CPU. Qu'est-ce que cela a à voir avec une valeur en cours de déplacement ? Marchons à travers ça. Tout d'abord, nous créons s1. Un pointeur d'une longueur et d'une capacité sont poussés sur la pile. La valeur de capacité dans ce cas est 3, la longueur est 3, et le pointeur pointe vers certains octets nouvellement alloués sur le tas. Ceci est tout à fait la valeur de la chaîne s1. Ensuite, nous déplaçons la valeur de s1 à s2, parce que nous ne pouvons avoir qu'un seul propriétaire. Cela fonctionne comme ceci : le pointeur, la longueur et la capacité sont tous copiés à partir de s1 et poussés en tant que nouvelles valeurs sur la pile dans le cadre de s2. Si nous nous arrêtions ici, alors la sécurité de la mémoire serait inexistante, c'est pourquoi Rust invalide immédiatement s1. Il existe toujours sur la pile, le compilateur considère simplement s1 maintenant non initialisé et ne vous laissera pas l'utiliser après ce point. C'est plus qu'une copie superficielle, c'est un mouvement, c'est pourquoi nous ne pouvons plus utiliser s1. La valeur a été déplacée vers s2. Techniquement, si s1 était mutable, nous pourrions lui attribuer une nouvelle valeur, puis l'utiliser à nouveau. Mais comme il est immuable dans cet exemple, c'est juste des ordures et nous ne pouvons plus l'utiliser. Que faire si nous ne voulions pas déplacer la valeur mais la copier à la place ? Pour faire une copie de s1 nous appelons la méthode clone (). Pourquoi est-il appelé clone au lieu de copie ? Laissez-moi vous expliquer ce qu'il fait sous le capot, et je vous le dirai. Clone effectue la même copie initiale de la pile, mais copie également les données du tas et ajuste le pointeur de s2 pour le pointer vers elle. Dans les situations de déplacement et de clone les trois règles sont remplies : 1) les valeurs ont des propriétaires, et 2) un seul propriétaire, et 3) lorsque les variables sortent de la portée, les valeurs seront immédiatement supprimées. Les données de pile et de tas, s'il y a des données de tas, forment ensemble une valeur. Rust réserve le terme copie lorsque seules les données de pile sont copiées. S' il y a des données de tas et des mises à jour de pointeurs impliqués, alors nous utilisons le terme clone. Dans d'autres langues vous pouvez appeler un clone une copie profonde. Lorsqu'une valeur est supprimée cela signifie que le destructeur, s'il y en a un, est immédiatement exécuté, la partie de tas est immédiatement libérée et la partie de pile est immédiatement affichée. Donc, pas de fuites, pas de pointeurs pendants, oui programmeur heureux ! Une autre situation de déplacement : Commençons par la même chaîne et faisons une fonction qui prend une chaîne et ne retourne rien. Si nous passons s1 à cette fonction, s1 est déplacé dans la variable locale s dans do_stuff (), ce qui signifie que nous ne pouvons plus utiliser s1, car il a été déplacé ! Alors, qu'est-ce qu'on fait ? Une option est de le déplacer lorsque nous aurons fini. Nous allons juste rendre s1 mutable, ajouter un type de retour à do_stuff (), puis retourner s comme expression de queue, qui est déplacé de la fonction et utilisé pour réinitialiser s1, mais ce n'est généralement pas le modèle que vous voulez ! Passer la propriété d'une valeur à une fonction signifie généralement qu'une fonction va consommer la valeur passée . Pour la plupart des autres cas, vous devriez utiliser des références, c'est pourquoi il est temps de parler de références et d'emprunt dans notre prochaine vidéo. 18. Références et empruntant et emprunt: Maintenant que nous sommes passés à la propriété, nous pouvons parler de références et d'emprunts. Au lieu de déplacer notre variable, utilisons une référence. Voici à nouveau notre fonction do_stuff (), mais cette fois, il faut une référence à une chaîne. L' esperluette antérieure à ce type indique une référence à un type. Lorsque nous appelons do_stuff (), nous lui passons une référence à s1, et s1 conserve la propriété de la valeur. do_stuff () emprunte une référence à la valeur. La référence, et non la valeur, est déplacée dans la fonction. la fin de la fonction la référence sort de son champ d'application, et la référence est supprimée, donc notre emprunt se termine à ce stade. Après l'appel de fonction, nous pouvons utiliser s1 comme normal, car la valeur n'a jamais été déplacée. Sous le capot, lorsque nous créons une référence à s1, Rust crée un pointeur vers s1, mais vous ne parlerez presque jamais de pointeurs dans Rust parce que le langage gère automatiquement leur création et leur destruction pour la plupart, et s'assure qu'ils sont toujours en utilisant un concept appelé lifetimes. Les durées de vie peuvent être résumées comme une règle selon laquelle les références doivent toujours être valides, ce qui signifie que le compilateur ne vous permettra pas de créer une référence à surpasse les données qu'il référencera en fin de compte, et vous ne pouvez jamais pointer vers null. Les références par défaut à immuable, même si la valeur référencée est mutable, mais si nous faisons une référence mutable à une valeur mutable , nous pouvons utiliser la référence pour changer la valeur aussi. La syntaxe d'une référence mutable est un peu spéciale : esperluette, muet, espace, variable ou type. Maintenant, attendez, pourquoi n'avons-nous pas dû déréférencer la référence mutable afin de modifier s dans la méthode do_stuff ()  ? Regardez ceci : nous utilisons la même syntaxe point pour accéder à une méthode de chaîne sur une référence mutable que nous faisons pour la valeur elle-même. Comment ça marche ? L' opérateur point d'une méthode ou d'un champ se déréférente automatiquement jusqu'à la valeur réelle. Donc, au moins lorsque vous traitez avec l'opérateur de point, vous n'avez pas à vous soucier de savoir si quelque chose est une valeur ou une référence ou même une référence d'une référence. Si nous déréférencons manuellement s, cela ressemblerait à ceci : vous utilisez un astérisque immédiatement avant une référence au déréférencement à la valeur, similaire à C. L' opérateur de déréférencement a une priorité assez faible, donc vous devrez parfois utiliser des parenthèses. Avec la plupart des autres opérateurs, comme l'opérateur d'affectation par exemple vous devrez déréférencer manuellement votre référence si vous souhaitez lire ou écrire sur la valeur réelle. Ici, je suis en train de déréférencer s afin que je puisse remplacer la valeur entière. Alors, arrêtons-nous et revoyons à quoi ressemblent les références. Si vous avez une variable X, cela crée une référence immuable à la valeur de cette variable, ce qui crée une référence mutable à la valeur de cette variable. De même avec les types, si c'est le type de votre valeur, alors c'est le type de votre référence immuable, et c'est le type de votre référence mutable. Dans l'inverse, si votre variable est une référence mutable à une valeur, alors déréférencer x vous donne un accès mutable à la valeur, et si x est une référence immuable à une valeur, alors déréférencer x vous donne un accès immuable à la valeur. Naturellement, puisque les références sont implémentées via des pointeurs, Rust a une règle spéciale pour nous garder en sécurité. À tout moment vous pouvez avoir exactement une référence mutable ou n'importe quel nombre de références immuables. Cette règle s'applique à tous les threads. Lorsque vous considérez que les références à une variable peuvent exister dans différents threads, cela commence à rendre assez évident pourquoi il n'est pas sûr d'avoir plusieurs références mutables à la même variable en même temps sans un certain type de verrouillage, mais si toutes les références sont immuables alors il n'y a pas de problème. Ainsi, vous pouvez avoir beaucoup de références immuables réparties sur plusieurs threads. Toutes ces règles dont je parlais sont appliquées par le compilateur, et par forcée, je veux dire des erreurs de compilateur, beaucoup d'erreurs de compilateur ! Au début, tu es comme Aaargh ! Je déteste le compilateur ! Ça ne cesse de me donner des erreurs ! Mais alors que vous obtenez le coup de cela vous réalisez que vous n'obtenez plus de segfaults mystérieux, et les messages d'erreur sont vraiment assez informatifs ! Et si le code compile, cela fonctionne ! Et c'est un sentiment incroyable ! Dans la prochaine vidéo, nous allons en apprendre davantage sur Structs. 19. Exercice E - Propriétaire et références: Exercice E - Propriété et références. Encore une fois, je vous encourage à essayer de faire cet exercice par vous-même et à revenir ensuite. D' accord. Maintenant que tu as eu la chance de le faire toi-même, passons à travers ça. Ce code obtient le premier argument sous forme de chaîne ou imprime l'utilisation et se ferme si aucun argument n'a été fourni. Ensuite, nous avons : écrire une fonction, inspecter, qui prend une référence à une chaîne, ne retourne rien, mais imprime si le contenu de la chaîne est pluriel ou singulier. Donc, le but de cette fonction est de prendre une référence à une chaîne afin que nous puissions regarder cette chaîne et ensuite imprimer quelque chose. Je vais aller de l'avant et faire cette fonction ici en ligne. C' est un mauvais style. Ne fais pas ça dans la vraie vie. On le déplacera vers le bas plus tard. Notre fonction est nommée inspect et elle prend une référence à une chaîne. Je vais juste appeler ça s. La fonction ne retourne rien, donc nous pouvons aller directement au corps de la fonction. Nous savons que nous allons imprimer quelque chose, alors ajoutons une ligne d'impression. Faisons de celui-ci la ligne d'impression plurielle, puis nous allons la copier et la changer au singulier. Pour que ceux qui fonctionnent, nous devons passer nos références de chaîne aux deux lignes d'impression. Le but de la fonction est d'imprimer l'un ou l'autre, alors ajoutons notre instruction if et vérifions si s se termine par « s ». Si c'est le cas, nous allons imprimer que c'est pluriel. Si ce n'est pas le cas, on imprimera que c'est singulier. Donc, notre méthode d'inspection prend s, qui est une référence à une chaîne. Il appelle alors s.ends_with. Le point déréférence jusqu'à la valeur et appelle la méthode ends_with sur la valeur. Nous lui passons une tranche de chaîne empruntée « s ». et puis si cela se termine par s, nous imprimons c'est pluriel, sinon nous imprimons c'est singulier. Essayons ça. Si nous faisons « cargaison run — pomme », nous devrions voir « pomme est singulière ». Super. Si nous ajoutons un s, alors nous verrons « les pommes sont plurielles ». Maintenant, je vais nettoyer après moi-même très rapidement en mettant cette fonction en dehors de principale. Maintenant, faisons le numéro deux. Je vais décommenter ce code, puis nous allons écrire une fonction qui prend une référence mutable à une chaîne. La fonction est appelée changement. Je vais appeler l'argument s, encore une fois. Notre tâche cette fois est d'ajouter un s à la fin du mot s'il ne l'a pas déjà. Cela va commencer vraiment de la même manière que notre dernière fonction. Nous allons utiliser s.ends_with (« s »), seulement cette fois nous nous soucions si cela ne se termine pas par s. Si ce n'est pas le cas, alors nous allons utiliser la méthode push_str pour ajouter un s à la chaîne. Cela ne fonctionne que si nous avons une chaîne mutable, et nous avons une référence mutable à une chaîne mutable, donc cela fonctionnera. Maintenant que j'ai ceci écrit, déplacons-le vers le bas du fichier où il devrait aller. Allons l'essayer avec « cargaison run — pommes ». Voilà notre nouvelle sortie. Dans ce cas, nous sommes passés dans les pommes et il est venu par inchangé. Si je passe juste pomme, nous pouvons voir que notre fonction ajoute un s et imprime toujours la même chose. Bon, revenons en arrière et faisons le numéro trois. abord, décommentons ce code, puis nous allons faire une fonction appelée eat. Eat va prendre une chaîne par valeur, ce qui signifie qu'il va la consommer. Cela signifie que la variable passée dans cette fonction ne sera plus utilisable après l'appel de fonction. Cela signifie que la variable passée dans cette fonction ne sera plus utilisable après l'appel de fonction. Cette fonction renvoie un booléen. Pour déterminer la valeur de retour nous devons déterminer si cette chaîne commence par un « b » et contient un « a ». Si c'est le cas, alors nous voulons retourner vrai. Sinon, nous retournons faux. Allons essayer ça. Si « manger » retourne vrai Si « manger » retourne vrai Nous devrions voir « Peut être des bananes ». Si ça retourne faux nous devrions voir « Pas les bananes ». Tout d'abord, essayons-le avec de la pomme : pas des bananes. Super. Essayons maintenant avec des bateaux. Ok, peut-être des bananes. Jetons un autre coup d'oeil à cette expression « si ». Nous pouvons simplifier cela. Nous retournons vrai pour vrai et faux pour faux. Donc, retournons juste la condition elle-même. Faire une vérification ponctuelle rapide avec des bateaux nous assure que cela fonctionne toujours. Nos prochaines instructions disent essayer d'exécuter ce programme avec bateau, banane et raisins. D' abord, bateau. Ok, le bateau est singulier. Beaucoup de bateaux. Ça pourrait être des bananes. Ok, alors on le changera en banane. Ok, alors on le changera en banane. Singulier. Beaucoup de bananes. Ça pourrait être des bananes. Et puis nous ferons nos raisins, et ensuite nous obtenons : le raisin est pluriel. J' ai beaucoup de raisins. Pas des bananes. Ok, alors tout fonctionne. Je laisserai la dernière partie de l'exercice, le défi, à vous ! 20. Structures: Il est temps d'en apprendre davantage sur les structures. Dans d'autres langues vous avez des cours. Dans Rust vous avez des structures. Les structures peuvent avoir des champs de données, des méthodes et des fonctions associées. La syntaxe de la structure et de ses champs est le mot-clé struct, puis le nom de la structure dans la casse capital-chamel, RedFox dans ce cas, puis accolades, puis les champs et leurs annotations de type dans une liste séparée par des virgules, et en tant que agréable touche, vous pouvez également terminer votre dernier champ avec une virgule, de sorte que le compilateur ne vous crie pas quand vous ajoutez un autre champ et ne pensez pas à ajouter une virgule au champ avant. Cette fonctionnalité est répétée dans de nombreuses constructions Rust qui utilisent des virgules. Instancier une structure est simple, bien que verbeux. Vous devez spécifier une valeur pour chaque champ. Généralement, vous devez implémenter une fonction associée à utiliser en tant que constructeur pour créer une structure avec des valeurs par défaut, puis appeler cela. Les méthodes et les fonctions associées sont définies dans un bloc d'implémentation distinct de la définition de structure. Le bloc d'implémentation commence par impl, puis le nom de la structure dont vous allez implémenter les fonctions et les méthodes . C' est une fonction associée car elle n'a pas de forme de soi comme premier paramètre. Dans de nombreuses autres langues, vous appelleriez cela une méthode de classe. Les fonctions associées sont souvent utilisées comme des constructeurs dans d'autres langues, avec new étant le nom conventionnel à utiliser lorsque vous voulez créer une nouvelle structure avec des valeurs par défaut. Vous remarquerez que Self avec un S majuscule peut être utilisé à la place du nom de la structure à l'intérieur du bloc d'implémentation. Vous pouvez également simplement taper le nom de la structure si vous le souhaitez mais je recommande d'utiliser Self à la place. Créons notre RedFox comme ça. L' opérateur de portée dans Rust est deux deux-points, et nous l'utilisons pour accéder à des parties de choses de type espace de noms. Nous avons déjà utilisé l'opérateur de portée dans les instructions d'utilisation pour accéder aux éléments à l'intérieur des modules. Ici, nous l'utilisons pour accéder à une fonction associée d'une structure. Une fois que vous avez une valeur instanciée, vous obtenez et définissez des champs et appelez des méthodes avec la syntaxe point comme vous le faites dans la plupart des langues. Les méthodes sont également définies dans le bloc d'implémentation. Les méthodes prennent toujours une certaine forme de soi comme leur premier argument. À ce stade dans le tutoriel pour la plupart des autres langues j'irais sur l'héritage de classe pour un peu, ou structurer l'héritage puisque c'est ce que Rust appelle ses classes. C' est une brève discussion cependant, car il n'y a pas d'héritage de structure. Alors attendez Rust est un langage orienté objet ou non ? Eh bien, il s'avère que c'est une question de guerre religieuse parce qu'il n'y a pas de définition universellement acceptée de ce qu'est ou non un langage orienté objet, et puisque la communauté Rust ne fait pas de guerres religieuses personne ne se soucie vraiment de la réponse. La vraie question est : Pourquoi Rust n'a-t-il pas d'héritage structuré ? La réponse est qu'ils ont choisi une meilleure façon de résoudre le problème que nous souhaitons résoudre : Traits, ce dont nous allons parler dans la prochaine vidéo. 21. Traits: Traits. Les traits sont similaires aux interfaces dans d'autres langues et ils sont vraiment cool. Rust prend la composition sur l'approche de l'héritage. Tu te souviens de notre structure RedFox ? Faisons un trait appelé Noisy. Les traits définissent le comportement requis. En d'autres termes, les fonctions et les méthodes qu'une structure doit implémenter si elle veut avoir ce trait. Le trait Noisy spécifie que la structure doit avoir une méthode nommée get_noise () qui renvoie unetranche de chaîneempruntée tranche de chaîne si la destruction veut être bruyante. Donc, ajoutons une implémentation du trait Noisy pour RedFox. Jetons un coup d'oeil à comment cela fonctionne. Nous implémentons le trait Noisy pour la structure RedFox et notre implémentation de la méthode get_noise () requise est « meow », ce qui est tout génial, mais pourquoi déranger ? Nous aurions pu juste implémenter cette méthode pour RedFox directement sans impliquer un trait du tout. La réponse est qu'une fois que nous avons un trait impliqué, nous pouvons commencer à écrire des fonctions génériques qui acceptent toute valeur qui implémente le trait. Cette fonction prend un élément de type T qui est défini pour être tout ce qui implémente le trait Noisy. La fonction peut utiliser n'importe quel comportement sur l'élément défini par le trait Noisy. Donc maintenant, nous avons une fonction générique qui peut prendre n'importe quel type tant qu'elle satisfait le trait Noisy. C' est plutôt cool. Tant que l'un des traits ou la structure est défini dans votre projet, vous pouvez implémenter n'importe quel trait pour n'importe quelle structure. Cela signifie que vous pouvez implémenter vos traits sur n'importe quel type de n'importe où, y compris les composants intégrés ou les types que vous importez à partir d'un autre paquet. Et sur votre structure, vous pouvez implémenter n'importe quel trait qu'il soit intégré ou à partir d'un projet. Ici, j'ai implémenté le trait Noisy pour le type intégré u8, qui est un octet, donc notre fonction print_noise générique () fonctionne très bien avec les octets maintenant. Il existe un trait spécial appelé Copier. Si votre type implémente Copy, il sera copié au lieu de déplacer dans des situations de déplacement. Cela a du sens pour les petites valeurs qui s'intègrent entièrement sur la pile, c'est pourquoi les types primitifs simples comme les entiers flottants et les booléens implémentent Copy. Si un type utilise le tas du tout, il ne peut pas implémenter Copy. Vous pouvez choisir d'implémenter Copy avec votre propre type si votre type n'utilise que d'autres types Copy. Ok, passons par un exemple de l'endroit où les traits semblent vraiment briller. Disons qu'on a un match avec une oie, un Pégase et un cheval. Ceux-ci ont plusieurs attributs en commun : Ces deux peuvent voler. Ces deux-là peuvent être montés, et ces deux-là explosent. Vous pouvez voir comment il pourrait devenir vraiment difficile d'utiliser l'héritage d'objet pour définir le comportement correct que chaque objet final doit avoir. Mais c'est assez simple avec des traits. L' oie met en œuvre les traits de mouche et d'explosion, le Pegasus met en œuvre les traits de mouche et de balade, et le cheval met en œuvre les traits de balade et explose et nous sommes tous heureux. Il est intéressant de noter que les traits implémentent l'héritage afin qu'un trait puisse hériter d'un autre trait. Cela permet d'avoir une hiérarchie d'héritage de trait comme celle-ci, où le mouvement est le trait racine, exécution hérite du mouvement, et ainsi de suite. Supposons que nous ayons une hiérarchie de traits de dommages séparée avec un trait parent de dommages et un trait enfant d'explosion . Ensuite, une structure de cheval qui implémente monter et exploser doit également mettre en œuvre les traits parents : mouvement, course et dégâts. Faire hériter votre trait du trait parent signifie vraiment que quiconque implémente votre trait devra également implémenter les traits parents. Les traits peuvent également avoir des comportements par défaut, donc si vous concevez des structures et des traits suffisamment soigneusement, vous n'aurez peut-être pas à implémenter certaines des méthodes de trait  ! Si vous souhaitez implémenter le comportement de trait par défaut, dans votre définition de trait au lieu de mettre fin à votre définition de fonction ou de méthode par un point-virgule, ajoutez un bloc avec votre comportement par défaut. Ensuite, lorsque vous implémentez le trait pour votre structure, ne fournissez pas une nouvelle définition pour la méthode dont vous voulez utiliser l'implémentation par défaut. La présence d'une implémentation remplacera la valeur par défaut. Ceci est un exemple entièrement fonctionnel : la structure Robot au milieu implémente le trait Run, mais elle ne remplace pas la méthode run () par défaut. Donc, lorsque robot.run () est appelé à partir de main () en bas, il exécute la méthode run () par défaut définie sur le trait en haut et imprime : « Je suis en cours d'exécution. » Un gotcha que je devrais vraiment mentionner : vous ne pouvez pas définir les champs comme faisant partie des traits. Cela pourrait être ajouté à l'avenir si quelqu'un peut trouver la bonne façon de le faire. En attendant, la solution de contournement consiste à définir les méthodes setter et getter dans votre trait. Dans la vidéo suivante, nous passerons en revue quelques-unes des collections de la bibliothèque standard. 22. Exercice F - Structs et traits: Exercice F : Structures et traits. Comme toujours je vous encourage à aller faire cet exercice un essai vous-même. Si vous allez le faire, c'est le moment de faire une pause ! Numéro un : définissez un trait nommé Bite. D'accord. Trait nommé Morsure. Définissez une seule méthode requise. fn bit () qui prend un moi qui est une référence mutable à moi-même, et c'est tout ce dont nous avons besoin dans le trait. Nous appellerons cette méthode quand nous voulons mordre quelque chose. Une fois que ce trait est défini, vous devriez être en mesure d'exécuter le programme avec le chargement exécuté sans aucune erreur. Essayons ça. D' accord. Je prends une bouchée. La carotte est maintenant {percent_left : 80} La carotte est maintenant {percent_left : 80} Numéro deux : créez maintenant une structure nommée Raisins avec un champ qui suit le nombre de raisins restant. Si vous avez besoin d'un indice, regardez comment cela a été fait pour la carotte au bas de ce fichier. Cela va le faire pour qu'il puisse être imprimé. Nous allons juste dériver le trait de débogage pour nous. Notre struct Raisins. Un champ qui suit le nombre de raisins qui restent. Appelons ça le montant restant, et qu'est-ce que c'est ? Et une i32 ? Si on fait ça, rien ne se passera sauf que ça ne devrait pas s'écraser. Faisons en sorte qu'il ne s'écrase pas. Ne s'est pas écrasé, génial. Mettre en œuvre la morsure pour raisins. Ok, impl Morsure pour Raisins. Lorsque vous mordez un Raisins soustraire un de combien de raisins sont laissés. D' accord. Donc nous avons besoin de cette morsure de fonction là-bas. Copiez et collez cela. Donc nous avons une référence mutable à notre moi. Nous savons qu'il nous reste un montant. Donc, disons self.amount_left moins égal à un. Je n'ai rien à revenir, donc nous avons tous fini. En main () ! D' accord. Numéro quatre : décommenter et ajuster le code ci-dessous pour correspondre à la façon dont vous avez défini votre structure Grapes . Ok, amount_left, amount_left nous sommes bons là-bas. Nous allons donc créer un raisin avec 100 raisins. Ensuite, nous allons Grapes.morsure () et nous devrions voir « Mangez un raisin : » et il devrait montrer notre représentation de raisin ici avec 99. Alors sauvegardons. Clair. Cargo run. « Mangez un raisin : Raisins {amount_left : 99} » Bonne chose. Dernier nous avons le défi, que je vais vous laisser ! 23. Collections: Examinons quelques collections. Ceux-ci sont tous dans la bibliothèque standard. Vector est une collection générique qui contient un tas d'un type, et est utile où vous utiliseriez des listes ou des tableaux et d'autres langues. C' est la collection la plus couramment utilisée de loin. Lorsque vous créez un vecteur, vous spécifiez le type d'objet qu'il stockera un angle de parenthèses. Une fois que vous avez le vecteur, vous pouvez y insérer des valeurs. Les vecteurs agissent comme une pile, donc push ajoute des choses à la fin, et pop supprime l'élément à la fin du vecteur et le renvoie. Puisque les vecteurs stockent des objets de taille connue l'un à côté de l'autre en mémoire, vous pouvez y indexer. Si votre index est hors limites, Rust va paniquer, ce qui est beaucoup plus utile que de vous nourrir les ordures ! Alors vérifiez votre longueur avant d'indexer. Très souvent, vous connaissez les valeurs d'un vecteur lorsque vous le créez. Il y a une macro appelée « vec ! » avec un v en minuscule qui rend la création de vecteurs à partir de valeurs littérales beaucoup plus ergonomique. Les vecteurs vous donnent beaucoup de contrôle de bas niveau sur leur comportement, et ils ont une tonne de méthodes pour faire à peu près tout ce que vous pourriez vouloir : insérer, supprimer, diviser, épisser, trier, répéter, recherche binaire — tout est dans la bibliothèque standard. HashMap est une collection générique dans laquelle vous spécifiez un type pour la clé et un type pour la valeur, et vous accédez aux valeurs par clé. Dans certaines langues, vous l'appelleriez un dictionnaire. Le point entier d'une carte de hachage est de pouvoir insérer, rechercher et supprimer des valeurs par clé en temps constant. Lorsque vous créez une carte de hachage, vous spécifiez le type de la clé et le type de la valeur. Dans ce cas, la clé est un octet et la valeur est un booléen. Une fois que vous avez une carte de hachage, vous insérez des entrées avec la méthode insert (). Utilisez la méthode remove () pour obtenir une valeur. remove () retourne réellement une énumération appelée Option. Nous parlerons des énumérations dans la prochaine vidéo. Il existe également des méthodes pour obtenir des références à des valeurs et parcourir des clés, des valeurs ou des clés et des valeurs, soit en tant que références immuables ou mutables. Il y a un tas d'autres collections que je vais juste décrire rapidement. VecDeque utilise un tampon en anneau pour implémenter une file d'attente double, qui peut ajouter ou supprimer efficacement des éléments à l'avant et à l'arrière, mais tout le reste est un peu moins efficace qu'un vecteur normal. LinkedList a la distinction douteuse d'être rapide à ajouter ou supprimer des éléments à un point arbitraire dans LinkedList a la distinction douteuse d'être rapide à ajouter ou supprimer des éléments à un point arbitraire dans la liste, mais lent à faire absolument autre chose. HashSet est une implémentation de hachage d'un ensemble qui effectue des opérations de jeu vraiment efficacement. BinaryHeap est comme une file d'attente prioritaire qui apparaît toujours hors de la valeur maximale. BTreeMap et BtreeSet sont des implémentations de cartes alternatives et définies à l'aide d'une arborescence binaire modifiée. Vous choisissez généralement ces options sur les variantes de hachage uniquement si vous avez besoin des clés de carte ou des valeurs définies pour toujours être triées. Dans la vidéo suivante, nous parlerons dans les énumérations. 24. Enums: Maintenant, il est temps de parler d'énumérations. énumérations dans Rust sont plus comme des types de données algébriques dans Haskell que des énumérations de type C. Vous spécifiez une énumération avec un mot-clé enum, le nom de l'enum en majuscule et les noms des variantes dans un bloc. Vous pouvez vous arrêter là si vous voulez, et l'utiliser comme ça, auquel cas c'est un peu comme une énumération en C. Juste espace de noms dans l'énumération et vous partez. Cependant, la puissance réelle d'une enum Rust vient de l'association de données et de méthodes avec les variantes. Vous pouvez toujours avoir une variante nommée sans données. Une variante peut avoir un seul type de données, un tuple de données ou une structure anonyme de données. Une énumération est peu comme une union en C seulement beaucoup mieux. Si vous créez une énumération, la valeur peut être l'une de ces variantes. Par exemple : votre DispenserItem peut être un Vide sans données associées, mais vous pouvez dire qu' il s'agit d'un Vide. Ou il pourrait s'agir d'une munitions avec un seul octet, ou bien d'un Things avec une chaîne et d'un entier 32 bits signé. Ou ça pourrait être un lieu avec x et y i32s. Cela peut être n'importe lequel d'entre eux, mais seulement un à la fois. Mieux encore, vous pouvez implémenter des fonctions et des méthodes pour une énumération. Vous pouvez également utiliser dans les énumérations avec des génériques. Option est une énumération générique dans la bibliothèque standard que vous utiliserez tout le temps. Le T entre crochets d'angle désigne tout type. Vous n'avez pas besoin d'utiliser T, vous pouvez utiliser un autre identifiant valide, mais la chose idiomatique à faire dans Rust est d'utiliser T ou une autre lettre majuscule. L' enum Option représente quand quelque chose est absent ou présent. Si vous essayez d'atteindre une valeur nulle ou nulle comme dans d'autres langues, vous voulez probablement utiliser une option dans Rust. Vous avez soit une valeur enveloppée dans la variante Certains, soit vous avez Aucun. Je vais utiliser une option pour le reste des exemples de cette section. Étant donné que les énumérations peuvent représenter toutes sortes de données, vous devez utiliser des modèles pour les examiner. Si vous voulez rechercher une variante unique, vous utilisez l'expression « if let ». « if let » prend un motif qui correspondra à l'une des variantes. Si le motif correspond, alors la condition est vraie et les variables à l'intérieur du modèle sont créées pour la portée du bloc « if let ». Si le motif ne correspond pas, la condition est fausse. C' est assez pratique si vous vous souciez d'une variante correspondant ou non, mais pas aussi génial si vous avez besoin de gérer toutes les variantes à la fois. Dans ce cas, vous utilisez l'expression de correspondance, qui est correspondance, une variable dont le type prend en charge la correspondance, comme une énumération, du corps de la correspondance entre accolades, où vous spécifiez des motifs suivis de doubles flèches, qui sont des signes égaux suivis de supérieur aux symboles pointant vers une expression qui représente la valeur de retour de ce bras de la correspondance. Les expressions de correspondance exigent que vous écriviez un bras de branche pour chaque résultat possible. En d'autres termes, les modèles d'une expression de correspondance doivent être exhaustifs. Un seul trait de soulignement est un modèle qui correspond à n'importe quoi et peut être utilisé pour une branche par défaut ou n'importe quoi. Notez que même si vous verrez souvent des blocs comme l'expression d'un bras de branche, n'importe quelle expression fera l'affaire, y compris des choses comme les appels de fonction et les valeurs nues. Soit tous les bras de branche doivent rien retourner soit tous les bras de branche doivent retourner le même type. Rappelez-vous que si vous utilisez réellement la valeur de retour d'une expression qui se termine par une accolade comme match, si let, ou if, ou un bloc imbriqué, alors vous devez mettre un point-virgule après l'accolade de fermeture. Si vous n'utilisez pas la valeur de retour d'une expression entre crochets, Rust vous permet de tricher et de laisser le point-virgule. Si vous n'utilisez pas la valeur de retour d'une expression entre crochets, Rust vous permet de tricher et de laisser le point-virgule. Je veux parler un peu plus en profondeur de deux énumérations spéciales. Ce qui est si spécial à leur sujet, c'est qu'ils sont utilisés dans toute la bibliothèque standard, de sorte que vous les rencontrerez constamment. Tout d'abord, regardons un peu plus Option. Voici à nouveau la définition d'Option. Comme je l'ai dit plus tôt, Option est utilisée chaque fois que quelque chose peut être absent. Voici comment vous pouvez créer une variante None d'une option. J' ai spécifié le type que Certains enveloppera entre crochets après l'option. Notez que je n'ai pas d'instruction use apportant dans l'option scope ou ses variantes Certains ou None de la bibliothèque standard. Puisque Option et ses variantes sont tellement utilisées, elles sont déjà incluses dans le prélude standard, qui est la liste des éléments de la bibliothèque standard qui sont toujours mis dans la portée par défaut. Si vous utilisez Option avec un type concret, le compilateur déduira le type, ce qui signifie que vous pouvez laisser l'annotation de type hors de la déclaration la plupart du temps. Il existe une méthode d'aide pratique appelée is_some () qui renvoie true si x est la variante Some. Il y a aussi une méthode is_none () qui fait exactement le contraire. Option implémente le trait IntoIterator, de sorte que vous pouvez également le traiter comme un vecteur de 0 ou 1 éléments et le mettre dans une boucle for. Vous devriez lire les méthodes pour Option, car vous finirez par les utiliser beaucoup. Il y en a un tas d'autres que je n'irai pas. L' autre énumération importante est Result. Result est utilisé chaque fois que quelque chose peut avoir un résultat utile, ou peut avoir une erreur. Cela se produit particulièrement souvent dans le module io. Voici la définition de l'énumération Result. Tout d'abord, vous verrez que le type enveloppé par Ok et le type enveloppé par Err sont à la fois génériques mais indépendants uns des autres. Deuxièmement, l'annotation # [must_use] en fait un avertissement du compilateur pour supprimer silencieusement un résultat. Tu dois faire quelque chose avec ça. Rust vous encourage fortement à regarder toutes les erreurs possibles et à faire un choix conscient de ce qu'il faut faire avec chacune d'elles. Chaque fois que vous traitez avec un échec d'E/S est une possibilité, donc les résultats sont beaucoup utilisés là-bas comme je l'ai dit plus tôt. Voyons le voir en action ! Ici, j'apporte std። fs። File dans la portée, puis j'essaie d'ouvrir un fichier. Cela renvoie un résultat car le fichier risque de ne pas être ouvert correctement. Puisque j'ai abandonné le Result sans rien faire avec, je reçois cet avertissement du compilateur : « inutilisé `std። result። Result` qui doit être utilisé » Le point est : ignorer les erreurs n'est pas une chose sûre à faire ! Alors allons choisir quelque chose à voir avec notre Résultat. La chose la plus simple que vous pourriez choisir de faire est de déballer le Result avec la méthode unwrap (). Si le résultat est un Ok, cela vous donne la structure de fichier que vous vouliez. Si le résultat est une erreur, cela bloque le programme. Dans certains cas, le plantage du programme peut être ce que vous voulez. Dans tous les cas, vous avez le choix. Une autre option est la méthode expect (). C' est exactement la même chose que unwrap (), sauf que la chaîne que vous passez à expect () est également imprimée dans la sortie du crash, de sorte que vous pouvez vous fournir un peu de contexte personnalisé pour expliquer pourquoi le crash s'est produit. Tout comme Option, il existe des méthodes d'aide comme is_ok () et is_err () qui renvoient des booléens. Ici, nous savons que unwrap () ne se bloquera jamais parce que nous nous sommes assurés que c'était déjà un Ok. Bien sûr, vous pouvez toujours faire la correspondance de motif complet aussi. Ici, je fais correspondre sur un résultat et exécute différents blocs en fonction de ce que j'ai récupéré. Dans la prochaine vidéo nous parlerons des fermetures. 25. Exercice G - Collections et enums: Exercice G : Collections Enums. Comme c'est ma coutume, je vous encourage à aller prendre un coup de feu à cet exercice vous-même. Voyez si vous pouvez le compléter, s' amuser avec, puis revenir ! Bon, maintenant que tu as eu une chance, passons à travers ça. Quelqu' un tire des flèches sur une cible. Nous devons classer les plans. 1a : créer une énumération appelée Shot avec ces variantes. Ok. Enum Shot. Variantes : Bullseye. Suivant est Hit contenant la distance du centre en tant que f64. Dernier, une mademoiselle. Très bien, nous avons notre virgule à la fin. Vous aurez besoin de terminer 1b aussi bien avant de pouvoir exécuter ce programme avec succès, alors allons faire 1b. impl Shot, Ok, donc nous allons ajouter quelques méthodes ou une méthode à notre Shot. Implémentez cette méthode pour convertir un tir en points. Ok, donc cela signifie que nous allons devoir correspondre sur moi, ce qui est une énumération avec les valeurs possibles... on est un Shot። Bullseye et si c'est le cas, que dois-je retourner ? Ok, bien cette fonction renvoie un i32... cinq points. 5. Ensuite, si c'est un tir et c'est un Hit avec un certain x, alors allons jeter un oeil à notre x, et si x est plus petit que 3.0 alors nous sommes à 2. Sinon, on est un 1. Donc 2 points si nous sommes assez près de la cible pour qu'elle soit à moins de 3 unités de distance. il est à plus de 3 unités mais toujours sur la cible, alors il s'agit d'un 1. Mais il y a une meilleure façon d'utiliser cela, je pense. Je pense qu'on peut rendre ça un peu plus agréable si on utilisait des gardes. Donc ici, si nous allons « si x est inférieur à 3.0" juste là alors ce bras entier pourrait juste être le 2 et alors nous pouvons dupliquer cela et dire pour autre chose alors nous savons que c'est plus grand qu'un égal à parce que nous vérifions déjà le moins de. Donc, dans ce cas, c'est un 1. Je pense que c'est un peu plus agréable dans ce cas d'utiliser un garde ici, et puis la dernière nous avons Shot። Miss et dans ce cas nous sommes 0 points. Ça devrait nous couvrir. Allons essayer. Oups ! Qu'est-ce que j'ai fait de mal ? 35. Inattendu, hein ? Qu' est-ce que j'ai fait ? J' ai probablement fait quelque chose de mal ici. Oui. Regarde ça. Oui. Regarde ça. On a deux accolades de fermeture pour ce match. On y va. Allons essayer encore une fois ! Voilà. Ok, principal ! Simulez le tir d'un tas de flèches et la collecte de leurs coordonnées sur la cible. Donc, nous avons nos arrow_coords qui est un vecteur de coords. On a une petite fonction d'aide qui nous donne cinq coordonnées aléatoires, des coups aléatoires, qui sait où ils ont frappé ? Et puis on fait un vecteur vide de coups. Donc nous allons convertir les coordonnées en plans. Donc, pour chaque coordonnée dans arrow_coords, appelez print_description afin que nous puissions voir ce qu'il était, puis créez la variante correcte de Shot en fonction de la valeur de distance_from_center : inférieur à, entre, supérieur à. Ok, donc on va convertir les coords en plans dans cette étape. Donc, pour coord un arrow_coords. Nous devons d'abord appeler coord.print_description, puis nous devons ajouter la bonne variante de Shot au vecteur shots, alors disons que notre tir actuel soit... faisons correspondre sur notre distance_from_center. coord.distance_from_center Maintenant, c'est un virgule flottante et nous pouvons totalement faire des correspondances là-dessus ! Donc x si x est inférieur à 1,0. Alors, quelle sera notre chance. Ce sera un Shot። Bullseye et x si x est plus petit que 5.0... nous savons déjà qu'il est supérieur ou égal à un parce que nous avons juste vérifié au-dessus de nous. Dans ce cas, il va être Shot and Hit et la valeur va être notre x. et puis si elle est supérieure à cinq, cela va être une autre condition, alors utilisons notre joker ici et disons dans TOUS les autres cas, nous allons retourner un Shot ። Mademoiselle. Et comme nous utilisons la valeur de cette expression de correspondance, nous avons besoin d'un point-virgule ici. Maintenant, on a une chance. C' est une énumération. C' est une de ces variantes de Shot. Et qu'allons-nous y faire ? Nous allons prendre notre vecteur de tir et nous allons le pousser dessus. Les tirs poussent notre tir. Alors sortons ici et partons : la cargaison coule. Devrait avoir une sortie intéressante maintenant. On y va. Sortie aléatoire, nous ne faisons toujours pas les totaux. C' est bon. On met tout dans ce vecteur, mais on ne l'utilise pas encore. Si nous le faisons à nouveau, nous devrions voir notre aléatoire en action. Des nombres aléatoires. D' accord. Retournons en arrière. Numéro 3 : Boucle à travers chaque tir dans les tirs. Ok, donc pour le tir dans les tirs, et ajouter ses points au total, donc total += shot.points () Rappelez-vous cette méthode ? La méthode qu'on a créée ici au début ? points ()... et nous convertissons nous-mêmes, notre énumération, en un nombre que nous pouvons ensuite ajouter à notre total là, puis notre total de points final devrait réellement avoir du sens. Alors essayons ça. Clair. Course de cargaison. D' accord. Donc voici nos trucs aléatoires. Alors 4.3... c' est presque cinq, mais c'est toujours le cas, alors ce devrait être un point. Deux ! C' est moins de trois, donc ça fait deux points. Encore deux points. Alors, qu'est-ce qu'on fait, cinq ? Et puis un autre point : c'est un six. Un autre point. Sept ! Le total du point final est de sept ! Hé, regarde ça ! Ça marche. Différents nombres aléatoires, résultats différents, mais il semble que ça marche. Essayons encore. Voyons si on peut avoir un bullseye. Oh ! Zéro ! J'ai raté la cible à chaque fois ! Sept. Pas de bullseyes pour nous ce soir. D' accord. Oh, il y en avait un ! Bon, alors ! C'était un gros exercice, donc je n'ai pas fait de défi sur celui-ci. Donc, nous avons tous fini. 26. Closures: Vous rencontrerez des fermetures lorsque vous voulez générer un thread, ou lorsque vous voulez faire une programmation fonctionnelle avec des itérateurs, et dans d'autres endroits communs dans la bibliothèque standard. Alors apprenons à propos des fermetures. Une fermeture est une fonction anonyme qui peut emprunter ou capturer des données de la portée dans laquelle elle est imbriquée . La syntaxe est une liste de paramètres entre deux canaux sans annotations de type, suivie d'un bloc. Cela crée une fonction anonyme appelée fermeture que vous pouvez appeler plus tard. Les types d'arguments et la valeur renvoyée sont tous déduits de la façon dont vous utilisez les arguments et ce que vous renvoyez. Attribuons donc cette fermeture à une variable appelée add, puis appelons-la avec 1 et 2 qui seront ajoutés ensemble et retournés 3. Revenons à notre fermeture. Vous n'avez pas besoin d'avoir de paramètres. Vous pouvez simplement laisser la liste des paramètres vide. Techniquement, vous pouvez aussi laisser le bloc vide, mais ce n'est pas très intéressant. Ce qui est vraiment intéressant, c'est qu'une fermeture empruntera une référence aux valeurs dans la portée englobante. Regardons un exemple de cela. Ici, nous créons une chaîne s et puis nous créons une fermeture qui emprunte une référence à s, ce qui fonctionne parce que le « println ! » veut réellement une référence de toute façon. Ensuite, nous assignons la fermeture à la variable f, et chaque fois que nous appelons f, il imprime une fraise. C' est génial si votre fermeture ne dépasse jamais la variable qu'elle fait référence, mais le compilateur ne nous laissera pas envoyer cela à un autre thread car un autre thread pourrait vivre plus longtemps que ce thread. Heureusement pour nous, fermetures supportent également la sémantique des mouvements, de sorte que nous pouvons forcer la fermeture à déplacer toutes les variables qu'elle utilise en elle-même et à en prendre possession. Maintenant s appartient à la fermeture et il vivra jusqu'à ce que la fermeture elle-même devienne hors de portée et soit abandonnée. Donc, nous pourrions envoyer cette fermeture à un autre thread, ou le retourner comme la valeur d'une fonction, ou faire ce que nous voulons avec elle. Si vous voulez faire une programmation de style fonctionnel, les fermetures seront vos amis proches ! Appelez iter () sur un vecteur pour obtenir un itérateur et tout un tas de méthodes qui utilisent des fermetures seront disponibles pour vous. Voici un exemple d'utilisation de map () et d'une fermeture pour multiplier chaque élément d'un vecteur par 3, puis filter () et une fermeture pour ignorer toutes les valeurs qui ne sont pas supérieures à 10, puis fold () avec une valeur initiale et une fermeture pour additionner les valeurs restantes ensemble. Dans la vidéo suivante nous allons parler de threads. 27. Threads: filetage de rouille est portable. Donc, ce code que nous sommes sur le point de regarder devrait fonctionner sur Mac, Linux et Windows. Cela fonctionnera également sur tout un tas d'autres plateformes que je ne vais pas énumérer. Voici un exemple entièrement fonctionnel, mais assez vide,. Nous amenons le module thread dans la portée d'abord, puis dans notre fonction principale, nous appelons thread። spawn (). thread። spawn () prend une fermeture sans arguments. Cette fermeture est exécutée comme la fonction principale du fil. Donc, tout ce que nous voudrions faire dans notre fil, nous ferions ici. Une pratique courante est de simplement appeler une fonction à partir de la fermeture. Donc nous n'avons pas une grande, énorme, longue, fermeture en ligne. spawn () renvoie un handle de jointure. Avec ce handle, nous pouvons appeler join (), qui mettra en pause le thread sur lequel nous sommes jusqu'à ce que le thread que nous joignons soit terminé et terminé. Le thread que nous générons pourrait avoir une erreur comme une panique, ou il pourrait renvoyer une valeur avec succès au thread qui le rejoint. Donc, ce que nous obtenons de l'appel de jointure est un résultat qui enveloppe une valeur de succès possible renvoyée par le thread, ou une erreur si le thread paniquait. Le filetage est un peu lourd. La création d'un nouveau thread alloue une quantité de RAM dépendante du système d'exploitation pour la propre pile du thread, souvent quelques mégaoctets. Chaque fois qu'un processeur passe d'un thread à un autre, il doit faire un commutateur de contexte coûteux. Ainsi, plus vous avez de threads essayant de partager un cœur de CPU, plus vous aurez de frais supplémentaires dans le changement de contexte . Même ainsi, les threads sont un outil fantastique lorsque vous avez besoin d'utiliser le CPU et la mémoire simultanément, car ils peuvent fonctionner simultanément sur plusieurs cœurs, et réellement accomplir plus de travail ! Cependant, si vous voulez juste continuer à faire du travail pendant que vous attendez quelque chose comme les E/S de disque ou les E/S réseau, alors je vous encourage à regarder async/await, ce qui est une approche beaucoup plus efficace pour attendre simultanément les choses. 28. Exercice H - Closures et fils: Exercice H - Fermetures G Filetages. Dans cet exercice, nous allons passer en revue les fermetures et les fils. Si vous n'avez pas eu la chance de revoir cet exercice, s'il vous plaît le faire maintenant. Suspendre l'enregistrement. Reviens quand tu auras fini. D' accord. Maintenant que vous avez eu la chance de faire l'exercice, passons-y. 1a. Nous allons utiliser quelques méthodes fonctionnelles sur les itérateurs : filtre et carte, spécifiquement, pour atteindre nos objectifs. Donc 1a : nous devons utiliser la méthode de filtre avec une fermeture pour supprimer n'importe quel nombre pair. Alors faisons ça. Nous appelons filtre sur notre itérateur et nous lui donnons un prédicat, qui est une fermeture. L' argument passé à la fermeture sera une référence. Donc nous avons deux choix ici. Un, nous pourrions traiter x comme une référence ou deux, nous pourrions déréférencer x dans la liste des arguments. Je vais vous montrer tous les deux. abord, traiter X comme une référence : déréférencer x, mod par deux, et vérifier si elle est égale à zéro. L' autre façon consiste à ajouter une esperluette avant x dans la liste des paramètres. Puis x a déjà été déréférencé. 1b. Utilisons la carte pour carrer les valeurs. Carte prend également une fermeture. Et encore une fois, notre paramètre est une référence. Alors je vais aller de l'avant et le déréférencer. On veut caler les chiffres et on a fini. Cela devrait filtrer tous les nombres pairs, puis les carrés, puis retourner la somme. Où déréférencer une référence est une question de style. Dans ce cas, je pense que cela a plus de sens dans la liste des paramètres. Sinon, cela ressemblerait à ceci, ce qui pour moi n'est pas vraiment très lisible. Je vais juste annuler ça. Numéro 2 : Maintenant, nous allons générer un thread et nous allons l'appeler cette fonction de expensive_sum que nous venons de modifier. Je vais décommenter ce code et j'appellerai std። thread። spawn, qui prend une fermeture sans arguments. Le corps de fermeture doit être une expression. Un bloc est toujours une expression, mais un appel de fonction unique est également une expression. Donc ici, je peux simplement faire mon appel. Le vecteur est my_vecteur, et nous avons fini. Puisque la somme de frais prend my_vector par valeur, la fermeture deviendra automatiquement une fermeture de mouvement et prendra la propriété de my_vector, mais je voudrais rendre cela explicite. Donc je vais ajouter le mouvement avant la fermeture. Maintenant, nous sommes explicites qu'il s'agit d'une fermeture de déménagement. Maintenant, nous sommes explicites qu'il s'agit d'une fermeture de déménagement. Pendant que notre thread enfant est en cours d'exécution, le thread principal imprimera les lettres a, b, c, d, e, f avec une pause de 200 millisecondes entre les deux afin que nous puissions voir ce qui se passe. Notre fonction expensive_sum commence par une pause de cinq cents millisecondes, de sorte que println devrait venir au milieu des lettres imprimées. Nous ne joignons pas le handle n'importe où, donc la valeur renvoyée par le thread sera rejetée. Essayons ça. On va faire des cargaisons. On dirait que ça marche. Notre thread enfant a été lancé et la sortie est venue au milieu de la sortie de notre thread principal. 3. Obtenons cette valeur de retour à partir du thread. 3. Obtenons cette valeur de retour à partir du thread. Nous allons prendre notre poignée au fil. Nous allons nous y joindre, ce qui nous fera arrêter jusqu'à ce que le thread quitte. Cela retournera un résultat qui sera une erreur si le thread s'est écrasé ou une valeur à déballer si le thread s'est arrêté avec succès. Je vais aller de l'avant et supposer que le thread s'est terminé avec succès et nous allons planter autrement. Ensuite, on imprimera la somme. Allons essayer. Course de cargaison. Le thread enfant chaissive_sum est 20, donc cela a fonctionné. Le thread principal n'a pas rejoint le thread enfant avant que le thread principal ait fini d'imprimer les lettres. Le thread principal n'a pas rejoint le thread enfant avant que le thread principal ait fini d'imprimer les lettres. Numéro 4 : Cet exercice est moins d'un exercice et plus d'une chance de marcher à travers un code et juste jouer avec. Donc, je vais décommenter ça et marcher à travers ça avec vous. Tout d'abord, nous allons créer un canal illimité. Unbounded signifie simplement que le canal mettra en mémoire tampon autant de valeurs qu'il peut jusqu'à ce qu'il manque de mémoire. La création d'un canal renvoie toujours un côté d'envoi et un côté de réception. J' ai utilisé tx pour le côté émetteur, ou l'émetteur, et j'ai utilisé rx pour le côté récepteur. Ensuite, nous clonons le côté émetteur. Le clonage d'une extrémité d'un canal permet de communiquer à cette extrémité d'un canal de plusieurs façons. Pour cet exemple particulier clonage d'une extrémité d'un canal permet de communiquer plusieurs façons à cette extrémité d'un canal. Pour cet exemple particulier j'aurais pu utiliser les canaux du module mpsc standard, mais j'ai choisi de ne pas le faire, car en général je recommande toujours d'utiliser les canaux de la caisse transversale externe à la place. Ils ont beaucoup plus de fonctionnalités et de meilleures performances. Ensuite, nous générons un fil. Nous stockons le handle de jointure dans handle_a, donc c'est notre thread A. Nous avons un endroit où nous pourrions faire une pause, mais nous ne le faisons pas. Nous imprimons « Thread A : 1". Nous nous arrêtons pendant 200 millisecondes, puis imprimons « Thread A : 2", puis nous quitterons le thread avec succès. Pendant que cela commence, le thread principal s'arrête pendant 100 millisecondes pour donner au thread une chance démarrer et d'imprimer « Thread A : 1" Ensuite, nous lançons le thread B et stockons une poignée dans handle_b. thread B s'arrête également pendant 0 millisecondes. thread B s'arrête également pendant 0 millisecondes. Puisque le thread principal est en pause pendant 100 millisecondes, le thread A a déjà imprimé « Thread A : 1" et est au milieu de la pause pendant 200 millisecondes, nous devrions donc voir « Thread A : 1" puis « Thread B : 1". au milieu de la pause pendant 200 millisecondes, donc nous devrions voir « Thread A : 1" puis « Thread B : 1". Ensuite, le thread B s'arrête également pendant 200 millisecondes, ce qui devrait donner au fil A une chance d' imprimer « Thread A : 2", puis quitter, puis notre pause ici dans le fil B se terminera et nous devrions imprimer « Thread B : 2". Donc, dans l'ensemble, nous devrions voir : « Thread A : 1", « Thread B : 1", « Thread A : 2", « Thread B : 2". Allons voir ce que fait le thread principal. Le fil principal boucle à travers le récepteur, en imprimant tous les messages reçus. C' est pourquoi j'ai parlé de la transmission des chaînes ci-dessus comme des « impressions » parce que le fil principal vient les recevoir et de les imprimer. Ensuite, pour une bonne hygiène, nous rejoignons les poignées. Techniquement, cela n'est pas nécessaire car le thread principal ne passerait jamais la boucle de réception jusqu'à ce que tous les côtés d'émission aient été fermés. fermeture de tous les côtés de transmission entraîne la fermeture du récepteur une fois qu'il est vide. Donc, une fois que la boucle for avait vidé le récepteur, le récepteur aurait quitté, ce qui entraînerait la sortie de la boucle for, et donc nous savons par ce point que les threads sont déjà sortis. Mais pour une bonne hygiène, nous les rejoindrons de toute façon, juste au cas où le programme serait refactorisé. Essayons ça. Clair. Course de cargaison. Regardez la sortie. Juste ce qu'on attendait. Filetage A : 1, Filetage B : 1, Filetage A : 2, Filetage B : 2. Changons nos horaires et voyons si les choses s'ajustent comme nous nous y attendons. Ajoutons une pause de 500 milliseconde au début du thread A. Maintenant, je m'attendrais à voir Thread B : 1, Thread B : 2, Thread A : 1, Thread A : 2. Essayons ça. Exécutons cette dernière commande à nouveau. Comme on s'y attendait ! Maintenant que le thread A a cette grande pause au début, le thread B sort en premier. Le défi consiste à inverser la direction des canaux afin que nous communiquions du thread principal aux fils enfants. Je vais te laisser comprendre ça. Notez que cela ne serait pas possible avec les canaux mpsc car ils ne peuvent avoir qu'une seule extrémité de réception du canal. C' est l'une des principales raisons pour lesquelles je recommande des voies transversales. Au moment où j'enregistre ce C'est l'une des principales raisons pour lesquelles je recommande des voies transversales. Au moment où j'enregistre cela je n'ai pas l'intention de faire une vidéo pas à pas pour l'exercice Z, qui est un petit projet amusant que vous pouvez relever seul comme un défi. 29. Invaders Partie 1 : Configurer l'audio: Faisons un projet. Faisons un simple jeu Space Invaders dans le terminal. On aura des sons. On aura du mouvement. On va tuer des extraterrestres. En cours de route nous utiliserons certains des concepts que nous avons appris dans ce cours. Ce projet est basé sur une présentation de codage en direct que j'ai faite pour le congrès virtuel open source pour O'Reilly le 18 juin 2020. Très bien, créons le projet. Je vais taper pendant que je parle, donc j'espère que vous aimez le son de mon clavier mécanique. Alors commençons. cargaison nouveaux envahisseurs Nous allons juste l'appeler envahisseurs pour court. J' ai préparé quelques sons, donc je vais juste les copier tous aux envahisseurs et ensuite nous irons dans envahisseurs. Ouvrons cela dans IntelliJ, que j'utiliserai. J' utilise également Visual Studio (Code). De grands outils, tous les deux ! Pour le moment je trouve qu'IntelliJ est un peu meilleur pour les présentations, mais VS Code arrive fort, donc je ne serais pas surpris si cela changait à l'avenir. Jetons un coup d'oeil à Cargo.Toml Nous devons ajouter nos dépendances. On va avoir besoin de crossterm. On va avoir besoin d'une bibliothèque audio. J' ai écrit rusty_audio pour le but. Il nous faut quelques minuteurs. J' ai mis ça dans rusty_time, une petite bibliothèque. Les deux sont sous mon moteur de jeu de bébé rusty_engine, à des fins éducatives. Nos dépendances prendront un peu à compiler. Alors allons faire ça maintenant. Donc, build cargo et cargo build —release juste au cas où nous voulons comparer les deux, nous allons juste obtenir toutes nos dépendances compilées. Pendant que c'est la compilation, allons configurer notre audio. Tout d'abord, dans main.rs, nous allons le faire retourner un résultat afin que nous puissions utiliser le point d'interrogation de manière ergonomique. Donc, nous ne nous soucions pas vraiment du succès. Mais pour les erreurs, cela va être : nous allons encadrer un objet de trait d'erreur dynamique. Et je dois importer une erreur, Mais pour erreur, cela va être : nous allons encadrer un objet de trait d'erreur dynamique. Et je suis arrivé à l'erreur d'importation, merci, IDE. Et puis sur cette ligne, nous pouvons commencer à configurer notre audio. Alors faisons un audio mutable, qui va être notre structure audio. Puis « new () », importez cela, c'est de rusty_audio. Ensuite, nous devons ajouter toutes nos sources audio à notre gestionnaire audio ici. Donc, le premier argument va être le nom. Ensuite, le chemin, tous nos fichiers se terminent par wav. Donc je vais y aller et ajouter ça, nous avons, voyons, un, deux, trois, quatre, cinq, six. Je peux les voir là-bas sur le côté. Quatre, cinq, six. Donc, nous allons juste ajouter chacun de ces éléments. Donc, d'abord on a « exploser ». Alors on a « perdre » pour quand on perd. Et on a « bouger » pour quand les méchants bougent et qu'on a « Pew », tu sais, « Pew, Pew » le laser qui va tirer. Et puis nous avons un son « startup » et un son « win ». D' accord. Maintenant, nous devons jouer le son. On va jouer au « démarrage » parce qu'on démarre. OK, si on s'arrêtait là , OK, si on s'arrêtait là on ne pourrait pas entendre ça, parce que le système audio lit l'audio dans un thread séparé en parallèle. Donc, si nous le laissons comme ça, nous courions à la fin de main, et cela provoquerait l'arrêt de tous les threads. Et pour qu'on n'entende rien. Donc, nous allons avoir cette section de nettoyage où nous disons audio.wait (), Donc, avons cette section de nettoyage où nous disons audio.wait (), bloquera jusqu'à ce que tout l'audio soit terminé la lecture, et ensuite nous devons retourner notre Ok. et ensuite nous devons retourner notre Ok. Maintenant, nous devrions être bons d'y aller, essayons ça. cargaison courir [musicalement] « bum-budda-bum ! » On y va. C' est notre son de démarrage. Oui, tous les sons me parlent dans un microphone. Si quelqu'un veut apporter des sons, ce serait fantastique. Je libère cette open source sur GitHub sous les licences MIT et Apache 2.0, votre choix. Donc si vous voulez venir et contribuer à ce projet après les vidéos ici, je vais juste le laisser continuer. Donc si vous voulez venir et contribuer à ce projet après les vidéos ici, je vais juste le laisser continuer. Si les gens veulent l'étendre, c' est bon ! Dans la section suivante, nous allons configurer notre rendu. 30. Invaders partie 2 : Rendu et Multithreading: Envahers Partie 2 : Rendu et Multithreading. Maintenant que notre audio fonctionne, initialisons le terminal. La première chose que nous devons faire est d'avoir accès à stdout. Ensuite, activons le mode brut afin que nous puissions obtenir l'entrée du clavier au fur et à mesure qu'il se produit. Nous allons utiliser notre opérateur de point d'interrogation ici, qui va essentiellement planter si nous avons une erreur. « terminal » est de crossterm. Ensuite, nous allons entrer dans notre autre écran. Nous allons utiliser une extension que le terme croisé fournit sur stdout appelée « execute » pour exécuter immédiatement quelque chose. Nous allons entrer dans l'écran alternatif. Si vous avez déjà utilisé vim ou emacs dans le terminal, vous remarquerez que lorsque vous le lancez, vous obtenez l' écran vim ou emacs, mais lorsque vous quittez, vous êtes de retour à l'endroit où vous étiez. C' est parce que les éditeurs utilisent l'écran alternatif. Il y a deux écrans disponibles dans le terminal. Et c'est ce qu'on va faire. Nous allons entrer dans notre écran alternatif, jouer à notre jeu, et ensuite nous reviendrons là où nous étions. Enfin, nous devons cacher le curseur. Nous ne voulons pas le voir rebondir pendant que nous rendons notre écran. D' accord. Maintenant que nous avons tout mis en place, nous devons aussi faire l'inverse à la fin. Sinon, nous resterons dans ce mode lorsque le programme se termine. Alors passons à la section de nettoyage et faisons tout à l'envers. Tout d'abord, montrons notre curseur. Ensuite, laissons l'écran alternatif. Et enfin, désactivons le mode brut. Et je dois me rappeler de poser un point d'interrogation ici aussi, sinon on aura un avertissement. D' accord. Maintenant, allons essayer ça. [ musique] « Bum budda bum ! » Ok, on y va. L' écran est devenu vide lorsque nous sommes entrés dans l'écran alternatif. Rien n'est encore là. Nous avons eu notre son et quand notre son a fini de jouer, nous avons tout annulé. Notre curseur était également caché lorsque nous étions sur l'écran alternatif. Donc, on dirait que tout fonctionne. Ensuite, nous allons mettre en place notre boucle de jeu principale. Donc, entre notre configuration et notre nettoyage, ajoutons notre boucle de jeu. Nous allons nommer la boucle de jeu « gameloop » C'est pour que nous puissions sortir de n'importe où dans la boucle par son nom. Et faisons un peu de gestion des entrées. Donc, c'est un peu verbeux, mais nous allons interroger pour les événements d'entrée. Donc, la fonction d'interrogation ici prend une durée. Nous allons donc utiliser la durée par défaut, qui est zéro. On ne va pas attendre. Nous reviendrons immédiatement s'il n'y a rien à faire. À ce stade, nous savons que nous avons un type d'événement, mais nous nous soucions uniquement des événements clés. Alors regardons ces spécifiquement. D' accord. Maintenant, nous savons que nous avons un événement clé, alors faisons partie de cet événement clé ou plus précisément, l'événement clé est Maintenant, nous savons que nous avons un événement clé, alors faisons correspondre à cet événement clé ou plus précisément, le code clé de l'événement clé. D' accord. Maintenant, nous examinons des codes clés spécifiques. Alors, qu'est-ce qu'on se soucie ? Eh bien, faisons ça pour qu'on puisse sortir si on appuie sur « Évasion » ou « Q ». Si nous obtenons l'un ou l'autre, alors, ok « q ». Si nous obtenons l'un ou l'autre, alors, d'accord, on a perdu le jeu parce qu'on est sorti tôt. Alors jouons, audio.play (« perdre »), et sortons de la boucle du jeu. Super. Maintenant, si une autre touche est pressée, nous allons juste l'ignorer. Et c'est notre première version de notre boucle de jeu. Je cherche juste une clé pour nous faire démissionner. Sinon, nous ferons une boucle infiniment. Allons essayer. [ comédie musicale] « Bum budda bum ! » D' accord. Nous sommes dans notre écran alternatif et nous traînons ici parce que la boucle de jeu nous attend pour appuyer sur une touche. Si j'appuie sur « j » rien ne se passe. Si j'appuie sur « q » [son] « vous perdez » Là, nous allons. Maintenant, allons faire une logique de cadre. Je pense que nous sommes prêts à commencer à faire une belle bibliothèque et à commencer à organiser notre code un peu mieux. Alors venons ici aux fichiers et je vais faire un lib.rs, qui sera la racine de notre bibliothèque. D' accord. Donc voici lib.rs. Faisons un « pub mod frame ». Nous allons avoir une logique de trame pour chaque trame que nous voulons rendre. Faisons aussi quelques constantes. Faisons « const NUM_ROWS » pour le nombre de lignes que nous allons avoir dans notre terrain de jeu. Réglez ça à 20. Et « pub const NUM_COLS » pour le nombre de colonnes que nous allons avoir. deux sont des usizes, et je vais mettre celui-ci à 40, et maintenant nous pouvons aller plus et créer notre frame.rs. Revenons à nos fichiers, créez un nouveau fichier Rust appelé frame.rs. Ce sera notre module de cadre. Et je vais aller de l'avant et utiliser un alias de type parce que notre cadre va être un vecteur de vecteurs de tranches de chaîne statiques empruntées, ce qui est non seulement une bouche à dire, mais c'est une bouche à taper. Donc, nous allons faire cet alias de type, il suffit de l'appeler Frame, vecteur de vecteurs de tranches de chaîne empruntées, statiques. Vous pouvez voir pourquoi on pourrait vouloir un alias pour ça. Bon, faisons une fonction de module public. Alors pub fn, et on l'appellera « new_frame ». Cela va juste générer un nouveau cadre pour nous. Et voici où nous bénéficions de notre alias de type, nous pouvons simplement retourner cadre. Ça va être un vecteur de vecteurs. Donc, nous devons d'abord faire notre vecteur externe. Donc, laissez mutable, nous allons l'appeler colonnes, Parce que nous allons faire colonne-major, donc nous indexons par colonne d'abord (x), puis ligne (y). Ça va être un vecteur. On va faire un peu d'optimisation ici. Je vais commencer avec la capacité, puisque je sais ce que c'est et que nous allons générer l'un de ces éléments, chaque frame, performance pourrait être importante. Et puis passons à travers chaque numéro. Je n'ai pas besoin du nombre réel, mais du nombre de fois où nous avons des colonnes. Et puis pour chacun d'entre eux, nous devons générer une colonne, puis à la fin, nous retournerons un [vecteur] de colonnes. Donc ici, nous allons aller de l'avant et dire notre vecteur de colonne individuel. Je vais juste nommer ce « col ». C' est aussi un vecteur. Nous connaissons aussi la capacité. Cette fois, ce sera NUM_ROWS. Et maintenant, nous avons juste besoin de faire une boucle et d'ajouter un seul caractère de ligne pour chaque ligne. Donc, pour le nombre de fois dans 0 à NUM_ROWS, col.push. On va faire un espace vide. Donc ça va être notre cadre vide. Nous allons créer un nouveau cadre avec notre logique à chaque fois autour de la boucle du jeu. Et puis nous devons nous rappeler de prendre des « cols » et de pousser sur le « col ». Et on y va. Ça devrait être le tout pour ajouter nos nouveaux cadres. Maintenant, tout ce que nous voulons voir va devoir être capable de se dessiner dans le cadre. Alors allons de l'avant et ajoutons un trait. Donc, je vais appeler ce trait « Drawable », donc « trait Drawable », et pour être Drawable, cela signifie que vous devez implémenter cette méthode, que j'appellerai « draw ». besoin d'implémenter cette méthode, que j'appellerai « draw ». Où vous prenez une référence immuable à vous-même et une référence mutable à un cadre, puis vous vous dessinez dans le cadre. C' est donc la définition de notre trait. C' est tout ce dont nous avons besoin pour notre logique de cadre. Revenons à notre lib.rs et ajoutons un autre module pour rendre notre cadre, notre cadre conceptuel, sur le terminal réel. Donc, nous allons séparer, en quelque sorte, notre modèle de notre point de vue, dans un sens. Allons donc « pub mod render » pour un module de rendu. Retournez à nos dossiers. Ajoutez un nouveau fichier Rust. Appelez ça un render.rs. Faisons un « pub fn rendu ». À quoi avons-nous besoin de rendre ? Eh bien, nous avons besoin d'un stdout. Donc, prenons standard comme une référence mutable et ensuite nous allons également faire un peu d'optimisation dans notre rendu. Nous ne rendrons que ce qui a changé. Pour ce faire, nous avons besoin d'un dernier cadre, qui sera une référence à un cadre, et d'un cadre actuel, qui sera également une référence à un cadre. Maintenant, c'est génial pour la plupart de nos cadres, mais au moins une fois, nous allons avoir besoin de tout dessiner. Donc, ajoutons également une option booléenne « force » pour forcer le rendu entier. D' accord. Donc, nous allons gérer le forçage d'abord. Donc, si nous forçons tout rendre, alors il y a quelques choses que nous voulons faire. Nous voulons effacer l'écran et je vais aller de l'avant et l'effacer en bleu. De sorte que lorsque nous dessinons notre terrain de jeu en noir, il sera décalé et nous pouvons voir la bordure du terrain de jeu. Nous allons donc profiter de crossterm, en faisant la file d'attente [trait] pour mettre en file d'attente un tas de commandes vers le terminal, puis les vider toutes en même temps. Donc « stdout.queue », nous allons définir la couleur d'arrière-plan sur la couleur bleue. Et puis nous allons planter s'il y a une erreur. Nous ne devrions voir un crash si notre programme n'est pas connecté à un terminal, donc je ne suis pas inquiet de frapper ça. Ensuite, nous devons réellement faire l'opération claire, donc « Clear » et quel est notre type clair ? Effacer le type est « Tous », nous allons effacer tout l'écran. Encore une fois, nous allons planter s'il y a une erreur. Et enfin, nous allons définir notre couleur de fond sur le noir. Alors allez-y et changez ça en noir pour que le terrain de jeu que nous dessinons ait un fond noir. Ensuite, nous devons parcourir notre cadre entier et dessiner tout ce qui a changé. Donc, pour chaque index x de vecteurs de colonne dans notre cadre actuel, que nous allons parcourir immuablement et énumérer pour obtenir cet index x, puis nous ferons exactement la même chose pour y et le caractère de chaîne réel que nous regardons dans notre colonne. Itérer à travers cela immuablement, énumérer cela pour obtenir le y. génial . Donc maintenant, nous avons l'index x et y et nous avons le caractère réel à l'emplacement de notre image actuelle. Alors comparons ça. Donc si — maintenant « s » va être une référence d'une tranche de chaîne empruntée, donc c'est double référencé. Donc, je vais le déréférencer d'un niveau afin que je puisse le comparer avec le caractère de la dernière image en x et y. Donc, je vais le déréférencer d'un niveau afin que je puisse le comparer avec le caractère de la dernière image en x et y. Donc, si le caractère a changé ou si nous sommes forçant le rendu, de toute façon, alors nous allons mettre en file d'attente une commande pour passer à l'emplacement correct. Voici donc notre « MoveTo », et puis notre x doit être un u16 pour cet appel et notre y aussi. Et puis, encore une fois, nous allons planter s'il y a une erreur. Et puis nous allons imprimer sans ligne, sans rinçage, un seul caractère à cet endroit, qui est notre « s » déréférencé à nouveau. Nous avons fait beaucoup de file d'attente, donc nous devons nous rappeler à la toute fin pour venir et sortir standard et vider tout ça. en un, grande mise à jour à l'écran. Alors voici notre rendu. Cela devrait prendre notre logique de cadre, puis itérer à travers les choses et rendre notre jeu au terminal lui-même. Cela devrait prendre notre logique de cadre, puis itérer à travers les choses et rendre notre jeu au terminal lui-même. Maintenant, nous avons notre logique. Nous avons notre rendu. On doit commencer à accrocher ces choses. Revenons donc à main et nous allons d'abord ajouter une boucle de rendu dans un thread. Donc nous allons faire un exemple de multithreading ici. Donc, après avoir configuré notre terminal, nous allons aller de l'avant et dire faisons une boucle de rendu dans un thread séparé. J' ai mesuré ça. En fait, il fournit un peu d'accélération. Nous n'avons probablement pas besoin d'accélérer, mais, vous savez, vous avez peut-être besoin de ça dans le monde réel. Alors, démontrons-le. Mettons en place un canal pour communiquer avec nos threads. Laissons donc un émetteur-récepteur de rendu dans un côté récepteur de rendu des canaux. Je vais utiliser les canaux mpsc juste parce qu'ils sont intégrés à la bibliothèque standard et ils sont super simples à utiliser dans une démo comme celle-ci. Maintenant, pour un vrai projet, je vous encourage à utiliser des canaux transversaux à la place. Ils sont plus performants et ils ont plus de fonctionnalités. Mais je vais juste m'en tenir au canal mpsc simple mort pour cet exemple. Faisons un fil maintenant. Donc, je veux attraper le handle de thread et cette variable, donc notre handle de rendu. Cela va être std። thread። spawn () Donc, je veux attraper le handle de thread et cette variable, donc notre handle de rendu. Cela va être std። thread። spawn () Et cela prend une fermeture et nous allons utiliser une fermeture de mouvement pour capturer notre extrémité du canal que nous utilisons. Et puis la logique ici va être la logique de notre fil. Ça ne va pas être beaucoup. Donc je vais juste le faire correctement en ligne plutôt que de faire une nouvelle fonction ou quelque chose comme ça. Donc la première chose dont nous avons besoin est qu'on ait besoin d'une variable pour tenir notre dernière image parce que nous allons tenir à cela et nous allons juste initialiser ça à un nouveau cadre vide. C' est un excellent point de départ. Ensuite, nous sommes dans un thread séparé. Nous devons rendre à la norme. Nous avons donc besoin de notre propre accès au standard out. Donc, je vais faire un autre « mut stdout », « io። stdout () ». Et puis nous sommes prêts à rendre l'écran entier une fois. Faites ce rendu, complet, forcé. C' est donc la fonction de rendu d'un module de rendu. Ici, nous lui donnons notre référence mutable à stdout sont référence immuable à last_frame. Nous n'avons pas de curr_frame donc je vais lui donner last_frame à nouveau. Et c'est bon parce qu'on va forcer tout rendre. D' accord. Maintenant, nous avons configuré l'écran une fois. Maintenant, nous pouvons faire nos mises à jour incrémentielles. Alors voici notre boucle. Nous avons besoin de notre cadre actuel. Donc ça va être rendu récepteur, recevoir. C' est là que notre fermeture de déménagement intervient parce que render_rx est là-haut sur la ligne 29 et nous le déplaçons dans ce thread. Maintenant, cela renvoie un résultat. Il renvoie soit une trame courante, soit si le canal a été fermé, soit renvoie une erreur. Donc, nous allons faire correspondre ce résultat. Et si c'est Ok (), si c'est en fait un Frame, alors nous allons juste retourner le Frame. Et si c'est un Err (), alors nous allons juste sortir de notre boucle de rendu, ce qui arrêtera notre thread enfant. Et si c'est un Err (), alors nous allons juste sortir de notre boucle de rendu, ce qui arrêtera notre thread enfant. D' accord. Donc, cela va retourner une trame actuelle, mais nous ne faisons rien avec ça. Alors allons-y et faisons une variable ici. Donc, laissez la trame actuelle être le résultat de cette expression de correspondance, ce qui signifie que nous devons ajouter un point-virgule ici parce que nous l'utilisons dans une instruction. Maintenant, nous sommes prêts à rendre notre cadre. Alors rendre, rendre. Donnez-lui notre stdout mutable. Donnez-lui notre référence à notre dernière image, une référence à notre cadre actuel que nous avons maintenant et faux. Nous ne forçons pas tout rendre. Il ne reste qu'une chose : nous devons faire un peu de ménage. Donc, la dernière image est maintenant la trame actuelle. Cela nous met en place pour la prochaine fois autour de la boucle. Nous avons la dernière image toute configuration correcte. Donc maintenant, nous avons un thread qui est prêt à recevoir des trames et à les rendre. Tout est branché, mais il ne reçoit rien. Alors commençons à le fournir avec de nouveaux cadres. Si nous revenons à notre boucle de jeu ici, la première chose que nous devons faire est au sommet. Nous avons besoin d'une section d'initialisation par trame. Voici donc notre init par image. Nous allons laisser notre cadre actuel être un nouveau cadre. Maintenant, nous voulons gérer l'entrée suivante afin que nous puissions apporter des modifications à notre logique avant de rendre. Alors revenons à après notre intervention. Et maintenant, nous pouvons faire une section de dessin et de rendu. Donc la première chose que nous voulons faire est d'envoyer ce cadre. Alors rendez, côté émetteur-récepteur. Envoyez notre cadre actuel. On n'en a pas besoin pour autre chose. Donc, nous sommes en fait en train de le déplacer vers un autre fil. Maintenant, cela renvoie un résultat. Mais nous nous attendons à ce que cela échoue les premières fois parce que cette boucle de jeu va commencer avant que ce thread enfant ne soit configuré et commence à recevoir. Donc, il n'y aura pas d'extrémité de réception du canal disponible pendant un petit moment. Donc, au lieu de planter sur une erreur ici, qui se bloquerait instantanément, nous allons ignorer complètement l'erreur. Donc, au lieu de planter sur une erreur ici, qui se bloquerait instantanément, nous allons ignorer complètement l'erreur. Alors, quel que soit le résultat soit simplement ignoré silencieusement. D' accord. Et je sais aussi, à travers les tests, que cette boucle de jeu est beaucoup plus rapide que notre boucle de rendu. Donc, je ne veux pas prendre du retard continu sur le rendu en rendant des centaines de milliers ou des millions d'images par seconde. Donc je vais ajouter un sommeil artificiel. Donc juste fil durée de sommeil et je n'ai vraiment pas besoin de dormir beaucoup. Je vais juste ajouter cette seule milliseconde de sommeil et ça suffira. Cela nous limite à seulement générer un millier d'images par seconde, ce que nous pouvons réellement suivre. Cela nous limite à seulement générer un millier d'images par seconde, ce que nous pouvons réellement suivre. Puisque nous ne faisons que mettre à jour ce qui a changé. Maintenant que nous avons cette configuration et l'envoi, nous avons encore besoin d'un dernier nettoyage. Nous devons venir ici et rejoindre ce fil de discussion. Maintenant, nous pourrions probablement ignorer ça et, vous savez, ne pas nous assurer que notre fil nettoie et juste tuer quand nous sortons. Mais c'est une meilleure hygiène de le nettoyer. Donc, je vais supprimer explicitement notre render_tx. Maintenant, les nouvelles versions de Rust ne nécessitent pas cela. J'ai testé ça. Les durées de vie non lexicales, je pense, ont permis à Rust de détecter qu'il n'était plus utilisé dans une portée plus petite et de le laisser tomber plus tôt. Mais les anciennes versions de Rust ne le sauront pas. Et donc on va faire ça ici. Nous serons un peu plus compatibles et explicites. Et puis une fois que le côté de transmission du canal a été fermé en le laissant tomber, le canal de réception va erreur et il sortira de la boucle que nous avons faite. Donc, nous savons qu'il est sûr d'appeler maintenant render_handle.join () et il n'attendra pas éternellement parce que nous savons que cette boucle de rendu va s'arrêter. Donc maintenant, nous avons fait ce beau nettoyage de notre fil. Nous arrêtons notre chaîne d'envoi. Cela déclenchera une sortie du thread, puis nous attendons jusqu'à ce que le thread se joint réellement. C' est ça. Allons essayer. C' est notre plus grande section entre essayer les choses. Donc, à l'avenir, nous serons en mesure d'essayer les choses plus fréquemment. Course de cargaison. [ musique] « Bum budda bum ! » Et on y va, on peut voir qu'on a effacé tout l'écran, le bleu. Et puis nous sommes passés et nous avons rendu notre terrain de jeu, qui est noir. Et maintenant, on peut passer à autre chose. Donc, dans la section suivante, nous allons regarder rendre notre joueur logique et le rendre à l'écran. 31. Invaders partie 3 : Le joueur: Envahers Partie 3 : Le Joueur. Oui, nous sommes enfin à l'endroit où nous allons mettre en œuvre le joueur. La première chose que nous allons faire est d'ajouter le module de lecteur. Donc « pub mod player ». Ensuite, continuons et ajoutons notre fichier player.rs. Nouveau fichier Rust. player.rs Maintenant, créons une structure pour notre joueur, donc « pub struct Player ». Qu' est-ce qu'on se soucie le plus ? Eh bien, nous nous soucions de x, donc sa position horizontalement. C' est usize. « y » aussi un usize. Et c'est vraiment à propos de ça à ce stade. Alors mettons en œuvre certaines choses. Quelle logique avons-nous besoin d'implémenter pour le joueur ? On a besoin d'un moyen de faire un joueur. Donc « pub fn new ». Je vais faire notre constructeur canonique ici. Nous rendrons notre Soi, qui est un Joueur. Le x que nous allons définir sur NUM_COLS divisé par deux. Cela nous mettra à peu près au milieu, et puis notre y nous mettrons à NUM_ROWS moins un. Donc, le « y » commence à zéro en haut de l'écran, puis à mesure que y augmente, nous descendons l'écran. Donc « NUM_ROWS - 1" va être notre dernière rangée jouable. Ne fais pas ce truc hors par-un ! Ensuite, faisons un moyen de se déplacer à gauche. Donc « pub fn move_left », moi mutable parce que nous avons besoin de changer nous-mêmes. Faisons quelques vérifications des limites. Donc, si self.x est supérieur à zéro alors « self.x -= 1". C'est à gauche. Déménageons maintenant. Donc « pub fn move_right » Donc « pub fn move_right » Juste la même chose, seulement dans la direction opposée. Encore une vérification des limites. Si self.x est plus petit que « NUM_COLS - 1" Voici notre chose hors par-un à nouveau. « self.x += 1" Ok, super. On peut se déplacer à gauche et se déplacer à droite. Que devons-nous faire d'autre ? Eh bien, je pense que c'est à propos de ça pour la logique. Mais nous avons besoin d'un moyen d'attirer notre joueur dans le cadre pour qu'il soit rendu. Allons donc vers le bas et implémentons Drawable pour le joueur, puis nous allons implémenter cette méthode de dessin. Donc, ici, nous avons accès à un cadre mutable. Utilisons-le. « frame », index par x et par y, et définissez-le sur le personnage qui représente notre joueur. Donc nous allons définir ça comme un « A ». Maintenant, ce n'est pas une majuscule A ! C' est un vaisseau ! C' est un vaisseau spatial ! Si vous regardez de haut en bas, le vaisseau spatial pointe vers le nord. Il y a cette barre transversale. Donc, évidemment, cette partie avant est, vous savez, où est le pilote. Donc, oui, évidemment un vaisseau spatial. Donc, maintenant, nous avons Drawable implémenté pour Player. Donc maintenant, nous avons toute la logique dont nous avons besoin pour afficher un joueur et le déplacer à gauche et à droite. Alors allons le brancher. Alors allons à main.rs. On va descendre à notre boucle de jeu ici. Juste avant notre boucle de jeu, nous aurons un joueur mutable. C' est « Player። new () » C'est « Player። new () » Et puis, allons à la section d'entrée et gérer le déplacement à gauche et à droite. Alors, les clés ici. Faisons KeyCode። Left. Donc, c'est la flèche gauche : qui déplace le joueur à gauche. Et si le code clé est, pariez que vous pouvez deviner où je vais avec cela, droite, player.move_right () player.move_right () Super. Donc, nous avons la gauche et la droite branchés à l'entrée. Nous faisons le joueur, nous le mettons à jour en fonction de l'entrée. Maintenant, attirons-le dans le cadre. Revenons donc à notre section « Dessiner et rendre ». Et juste avant de rendre le cadre, nous l'attirerons dans le cadre. Donc, le player.draw, donnez-lui cette image actuelle mutable. D' accord. Besoin d'importer le trait Drawable, sorte que cela fonctionne. Qu' est-ce qu'on manque ? Ah ! curr_frame... ne peut pas emprunter la variable locale immuable curr_frame comme mutable. Alors revenons ici et rendons ce curr_frame mutable. Sinon, nous ne pourrons jamais y attirer quoi que ce soit. Maintenant, je pense qu'on a tout branché. Allons essayer ! cargaison run cargaison run [comédie musicale] « Bum budda bum ! » Ok, nous y voilà. On voit le A au milieu. Essaie à gauche. Hé ! Ça ne sort pas du côté. Essaie bien... Ça ne va pas de ce côté non plus. Super ! Nous avons les débuts d'un jeu réel, nous avons un joueur que nous pouvons déplacer ici. [ son] « Tu perds. » Maintenant que nous avons un joueur, dans la section suivante, nous allons voir à propos de la prise de vue. 32. Invaders partie 4 : Prise de vue: Envahisseurs Partie 4 : Tir. Maintenant que notre joueur travaille, faisons en sorte qu'il puisse tirer un boulon laser vers le haut. Alors allons à lib.rs et ajoutons « pub mod shot ». Viens ici et fais un nouveau dossier Rust. Shot.rs Super. Donc on a un module de tir. Faisons une structure appelée Shot. Qu' est-ce que nous nous soucions des données ? Il a besoin d'un « x » et d'un « y ». Voyons aussi si le tir est en train d'exploser et nous aurons besoin d'une minuterie interne pour suivre notre mouvement. Mettons en œuvre notre logique de tir. Comme toujours, nous avons besoin d'un moyen de construire un tir. Alors faisons un nouveau. Et nous aurons besoin d'un X et d'un Y. et nous rendrons notre Soi. Donc notre Soi. Bien sûr, notre x sera x et notre y sera y. Nous pouvons utiliser des raccourcis sur ceux-ci. « exploser » sera faux parce que nous venons de commencer et le minuteur sera notre minuteur de rusty_time et nous allons en faire une minuterie de cinquante millisecondes. Donc notre laser va se déplacer vers le haut d'une cellule toutes les 50 millisecondes. Petit laser assez rapide. Petit laser assez rapide. Ensuite, nous aurons besoin d'un moyen de mettre à jour ce minuteur. Alors faisons une « mise à jour pub fn ». Il aura besoin d'un accès mutable à lui-même. Et passons-le dans une durée delta à partir de l'heure standard. D' accord. Ce que nous devons faire ici, c'est prendre notre self.timer et le mettre à jour avec un delta. Cela fera que le minuteur commence réellement à compter à rebours par quantité « delta ». Et puis vérifions. Donc si la minuterie est prête et qu'on n'explose pas, alors « ! qui explose ». Maintenant, on peut bouger. Donc la minuterie est prête à bouger et on n'explose pas parce qu'on ne veut pas continuer à bouger après avoir explosé. Alors faisons un peu de vérification des limites ici. Si self.y est plus grand que zéro. Donc, si nous n'avons pas encore atteint le haut de l'écran, alors nous pouvons avancer vers le haut. On ne veut pas quitter le haut parce qu'alors on sortira des limites. D' accord. Super. Donc maintenant, nous avons déménagé, si c'est le cas. Et de toute façon, nous devons réinitialiser la minuterie. Donc self.timer.reset (). Maintenant, nous avons la logique de déplacer notre tir laser vers le haut. Maintenant, faisons un moyen d'exploser réellement. « pub fn explode » Nous avons besoin d'un accès mutable à notre auto à nouveau afin que nous puissions changer cette variable qui explose, donc le champ self.exploding est vrai. Et puis prenons cette minuterie et 50 millisecondes n'est pas assez long pour voir une explosion très bien, et 50 millisecondes ne sont pas assez longs pour voir une explosion très bien, donc je vais quintupler ça. Prenons notre self.timer et mettez-le à une nouvelle minuterie. Et cette fois, ça va durer 250 millisecondes. Super. Ça nous donnera juste assez de temps pour voir l'explosion avant qu'elle ne disparaisse. Ensuite, faisons une fonction pour dire quand nous sommes morts. Donc quelqu'un d'autre va avoir besoin de nettoyer ça. En fait, ce sera notre joueur qui va gérer nos tirs. Il a besoin de savoir, il faut savoir, est-ce que c'est mort ? Doit-il être nettoyé ? Alors faisons un « pub fn mort » Alors faisons un « pub fn mort » Dans ce cas, nous avons seulement besoin de nous regarder, donc nous aurons un accès immuable. Retourner true ou false ? Sommes-nous morts ? Maintenant, ce n'est qu'une condition. Donc nous pouvons juste avoir une expression de queue booléenne ici. Donc, self.exploding et self.timer.ready signifie que nous avons explosé et que le temps est écoulé. Donc c'est une affaire. Ou si self.y est zéro, alors nous avons atteint le haut de l'écran et nous devons être nettoyés. Donc, la vérification des limites à nouveau. bornes, des bornes partout. Super. Donc maintenant, nous avons une logique pour un tir, mais nous ne pouvons pas le voir encore. Donc, il est venu ici et implémenter Drawable pour notre Shot. D' accord. Voici notre commande de tirage. Voici notre commande de tirage. Nous avons un cadre que nous pouvons indexer par self.x et self.y Cela devrait sembler un peu familier. Cette fois, cependant, au lieu de le mettre à un seul personnage, nous allons dire que si nous explosons, alors nous voulons évidemment être le personnage de l'explosion, qui est un astérisque, bien sûr. Sinon, nous voulons être un laser, qui est un tuyau dans ce cas. Et c'est tout pour notre logique de tir. Alors maintenant, passons au joueur et ajoutons la manipulation de tir au joueur. La première chose que nous allons faire est d'ajouter un vecteur de tir à notre structure de joueur ici. Je vais m'écarter du classique « Vous ne pouvez avoir qu'un seul coup » ici, et je vais dire que nous pouvons avoir plusieurs coups de feu. Donc je vais utiliser un vecteur, parce que je pense que c'est plus amusant. Ensuite, nous descendons à new (). Nous devons ajouter ce vecteur de tir. Donc, c'est un Vec። new () et alors nous devons ajouter la logique pour réellement tirer. Alors descendons et ajoutons un « pub fn shoot ». Nous allons avoir besoin d'un accès mutable à notre moi. Nous allons retourner un booléen indiquant si oui ou non nous avons réellement tourné avec succès, parce que si le nombre maximum de photos est déjà sur l'écran, nous pourrions ne pas réellement tirer avec succès. Mais si nous tournons avec succès, nous voulons jouer le son laser, le « pew pew ». Donc si la longueur de notre self.shots est plus petite que... je vais la mettre à deux. Je ne deviens pas fou ici. Vous pouvez avoir deux coups à la fois. Si vous voulez, vous pouvez régler cela un peu plus haut. Ensuite, notre self.shots doit être poussé dessus un Shot። new () Où ? Eh bien, c'est à notre propre emplacement x parce qu'il est directement au-dessus de nous, et comme il est directement au-dessus de nous, cela signifie self.y moins un, parce que l'axe y est inversé. Et alors vrai nous avons tiré, sinon faux nous n'avons pas tiré. Alors on y va. Voilà notre logique de tir. Ensuite, faisons un moyen de pomper ces minuteries sur nos plans. Nous avons donc besoin d'une « mise à jour pub fn », d'un accès mutable à notre auto, et nous avons besoin de cette durée delta. Et puis on a juste besoin de passer par chaque tir de nos coups de feu. Nous allons parcourir cela de façon muetable pour que nous puissions les changer, et appelons simplement shot.update avec notre delta. Et puis, bien sûr, nous devons nettoyer après nous-mêmes. Un tir a peut-être atteint le sommet, ou il a fini d'exploser. Donc, nous allons prendre nos coups et garder seulement le tir là où il n'est pas mort. Un peu de programmation fonctionnelle là-bas. On utilise une fermeture ici. C' est ce que conserve prend, et il appliquera cette fermeture à chaque élément du vecteur. C' est ce que conserve prend, et il appliquera cette fermeture à chaque élément du vecteur, et s'il retourne vrai, alors il le garde. S' il renvoie false, il le supprime du vecteur. Alors maintenant, passons à notre appel de tirage ici et dessinons nos tirs aussi. C' est donc assez simple aussi. Alors, allez simplement : pour tir dans les coups de soi. Itérez à travers cela immuablement parce que nous n'avons pas besoin de changer quoi que ce soit. On peut juste appeler Shot.dessiner le cadre. Frame est déjà une référence mutable à un cadre ici, donc nous n'avons pas besoin de changer quoi que ce soit. Nous avons donc mis en œuvre notre logique de tir. On l'a branché au joueur. Alors maintenant, revenons à la principale et assurez-vous que les choses circulent à travers. Alors revenez à main, allez à notre gestion des entrées. doit y avoir un moyen de faire en sorte que ce coup se produise. Allons donc dire que si le code de la clé est le caractère « barre d'espace » ou que le code de la clé est la touche « Entrée », alors... alors... Donc, nous voulons faire player.shoot, mais cela renvoie un booléen. Donc, si player.shoot, alors nous pouvons jouer notre audio : jouer notre « pew » son. Maintenant, nous avons la plus grande partie de la logique branchée. Mais qu'en est-il de ces minuteurs ? On n'a pas de minuteurs qui se pompent ici. Nous allons donc ajouter une section ici pour notre mise à jour de nos minuteries. Appelez ça « //Mises à jour » et nous allons juste faire player.update avec un delta et qui coulera vers le bas vers les coups. Mais qu'est-ce que delta ? On a un delta ? Non, on n'a pas encore fait de delta. Alors montons et ajoutons ceci. J' aime utiliser des deltas instantanés. Donc, si nous allons juste ici après notre joueur et dire laisser mutable instant, un Instant። now (). Instant est de l' heure normale. Ensuite, nous pouvons entrer dans notre initialisation par trame et avant de faire autre chose, disons simplement que notre delta pour cette trame soit notre temps écoulé depuis qu'il a commencé sa durée de vie. Maintenant, comme nous l'avons fait, nous devons alors, instantanément, venir mettre à jour notre instant au prochain « maintenant ». Maintenant, la prochaine fois autour de la boucle, nous aurons mesuré exactement le temps qu'il a fallu pour contourner la boucle. Maintenant, la prochaine fois autour de la boucle, nous aurons mesuré exactement le temps qu'il a fallu pour contourner la boucle. Maintenant, nous avons le delta. Donc maintenant ce player.update (delta) devrait fonctionner. Et je pense que nous sommes tous prêts à essayer ça ! Alors, sauvegardez ça. « course de cargaison ». [ musicalement] « Bum budda bum ! » Ok, on peut toujours bouger, et si je frappe l'espace (« pew ») ou entre (« pew ») ou les maintiens enfoncés. [ son] « Étain, Étain, Étain, Étain, Étain, Étain » Grand. Dans la section suivante, nous allons faire la logique pour nos envahisseurs. 33. Invaders Partie 5 : Invaders: Envaders Partie 5 : Dans laquelle nous créons les envahisseurs réels qui sont projet est nommé d'après. Faisons de nos envahisseurs module. Donc c'est « pub mod invaders ». Viens ici. Nouveau fichier de rouille « invaders.rs » Nouveau fichier de rouille « invaders.rs » Nouveau fichier de rouille « invaders.rs » Vous pouvez probablement deviner comment nous allons commencer ! « structure de pub... » Qu' est-ce qu'on va faire ? Faisons un seul Invader. Juste un envahisseur. Il a un « x », qui est un usize, un « y », aussi un usize, et c'est tout. Donc ça va juste être notre envahisseur. Il a un emplacement et c'est tout ce qu'on tient vraiment à lui en ce moment. Parce que plus important qu'un envahisseur individuel est notre « pub struct Invaders ». Ça va être notre gestionnaire d'envahisseurs ou notre armée d'envahisseurs. Alors faisons une « armée de pub », qui est un Vec d'Invader, parce qu'ils font tous la même chose, et ils vont avoir un minuteur de mouvement pour déplacer toute l'armée autour. Nous allons avoir une direction, qui va être à gauche ou à droite, principalement, bien qu'ils se déplacent vers le bas quand ils frappent le côté. D' accord. « minuteur » est de rusty_time. Allons mettre en œuvre notre logique. Alors, quelle logique avons-nous besoin d'implémenter pour nos envahisseurs ? Eh bien, évidemment, ils ont besoin d'un « nouveau », donc « pub fn new » renvoie un Soi. Et on va faire une armée mutable parce qu'on va pousser des choses dessus. C' est un Vector። new () d'envahisseurs. Et puis pour chaque « x » dans tous les x possibles sur notre terrain de jeu, et pour chaque « y » dans chaque rangée sur le terrain de jeu, assurez-vous de les importer. D' accord. Et tu sais quoi ? C' est vraiment ennuyeux qu'il ait cet avertissement. Alors ajoutons notre petit retour ici à la fin et revenons et finissons la logique. Donc Soi est ce qu'il va revenir. Qu' est-ce que ça va retourner pour l'armée ? Eh bien, ça va être l'armée. Pour le minuteur de mouvement donnons-lui un Timer። from_millis. Réglons ça à 2 secondes. C' est 2000 millisecondes. Et la direction dans laquelle nous allons commencer est correcte. Si positif est à droite. Donc c'est ce que nous revenons. Remplissons cette armée pour qu'elle ne soit pas vide. Alors fais juste un peu de maths ici. J' ai en quelque sorte compris ça. Ça fait une belle grille. Si nous disons, d'accord bien, si... nous ne voulons pas x tout le chemin sur le côté gauche donc ça doit être un peu. Il doit donc être plus grand qu'un. Et disons la même chose de l'autre côté. x doit être plus petit que NUM_COLS - 2 Donc, il doit être la même quantité dans de l'autre côté, mais c'est 2 au lieu de 1 à cause de notre chose hors par-une. D' accord. Et puis de la même façon, nous ne voulons pas tout ce chemin au sommet. Donc y doit être plus grand que zéro. Rappelez-vous que y augmente, nous descendons l'écran. D' accord. Nous voulons aussi nous arrêter quelque part à mi-chemin dans l'écran. Donc je vais juste coder ça dur. Vous voudrez peut-être faire quelque chose de plus intelligent ou basé sur NUM_ROWS, mais je vais juste dire que y est plus petit que 9. Ça semblait bien fonctionner. Je vais juste dire que y est plus petit que 9. Ça semblait bien fonctionner. Et puis je vais dire : Ok, il doit être sur un X pair. Je ne veux pas qu'il soit sur chaque cellule. C' est un peu fou. Donc, nous allons dire x mod 2 est égal à 0. Donc, c'est seulement sur pair et même chose avec y. donc y mod 2 est égal à 0. Donc ça devrait nous donner une grille d'envahisseurs décemment clairsemée. Alors poussons un envahisseur, alors armée.poussez notre envahisseur. Je n'ai pas pris la peine de faire une « nouvelle » chose pour eux parce que c'est le seul endroit où l'accès à nouveau. Alors, que diable ? Il suffit de faire la syntaxe complète et nous l'avons là. Défilons un peu. Il y a notre logique complète pour notre nouveau. On va faire un peu de logique de l'armée maintenant, alors je vais aller de l'avant et rendre ça un peu plus large. Ensuite, nous devons nous mettre à jour pour avoir une minuterie et un mouvement militaire ici et ça va prendre un peu de temps. Ensuite, nous devons nous mettre à jour pour avoir une minuterie et un mouvement militaire ici et ça va prendre un peu de temps. Alors supporte avec moi ici. C' est probablement notre méthode la plus longue du projet. Donc « pub fn update » il va prendre son accès mutable à lui-même et une durée delta. Il faut mettre à jour ces temps, et nous allons revenir si l'armée entière a bougé ou non, parce que si c' était le cas, nous allons jouer ce son de mouvement. Donc self.move_timer doit être mis à jour. Mettons-le d'abord à jour avec le delta pour ne pas oublier ça. Maintenant, nous pouvons faire notre logique de déplacement. On va bouger du tout ? Si nous ne le sommes pas, alors revenons juste faux. Si nous sommes, cependant, allons-y si le minuteur est prêt si le minuteur est prêt alors nous allons faire tout un tas de choses et à la fin nous reviendrons vrai, alors nous allons faire tout un tas de choses et au fin, nous allons retourner vrai, vous avez réellement déménagé. Maintenant, nous pouvons faire notre logique juste ici, au milieu. La première chose que nous voulons faire, puisque le minuteur de mouvement était prêt, nous devons le réinitialiser. Donc self.move_timer.reset () Donc self.move_timer.reset () Grande. C' est hors du chemin. Ensuite, nous devons déterminer : est-il temps de descendre ? Donc, faisons un « mut vers le bas » et par défaut que false. Essayons maintenant de dire si nous devons aller vers le bas plutôt que vers la gauche ou la droite. Donc si je déménage actuellement à gauche, ce qui est négatif, alors nous allons obtenir notre minimum x de toute l'armée. Voici donc un autre bon endroit pour une programmation fonctionnelle. Donc, nous allons faire notre self.army, qui est un vecteur, iter () pour obtenir un itérateur immuable, puis nous allons mapper chaque envahisseur à sa valeur x, puis prenons la valeur x minimale et puis c'est faillible, parce que si le vecteur était vide, par exemple, il pourrait pas être dans un minimum. Donc nous allons faire le unwrap_ou nous donner un zéro si vous ne trouvez rien. Donc maintenant, nous avons un minimum X ou ce sera zéro s'il n'a rien trouvé. Donc, si min_x est zéro, alors vous êtes tout le chemin sur le côté gauche de l'écran. Donc, en fait, vous devez descendre au lieu de gauche. Mais la prochaine fois que vous vous déplacerez vers la droite, alors changeons la direction vers la droite, et réglez vers le bas sur true. Et puis plus tard dans cette méthode, nous saurons que nous devons aller vers le bas plutôt que vers le droit. Ok, maintenant sinon, nous essayons de bouger à droite. sinon, nous essayons de bien aller. Mais on a peut-être touché le bon côté. Donc on va faire exactement la même chose, seulement pour le bon côté des choses. Cette fois, on va chercher un max_x , donc on va faire la même chose que Max. Donc self.army.iter (), mappez notre envahisseur à sa valeur x nouveau. Prenez le max et puis déballer ou nous dirons zéro parce que peu importe quelle valeur est que nous donnons en retour. Donc, si max_x est NUM_COLS - 1, cela signifie que nous avons atteint notre côté droit, et donc notre direction doit retourner. Donc négatif encore et vers le bas est vrai. D' accord. Donc, à ce stade, nous savons que nous déménageons. Et nous savons si oui ou non nous allons vers le bas ou sur le côté. Allons donc gérer ce mouvement réel. Faites défiler un peu ici. Juste ici. Si on descend, alors on descend. Sinon, on déménage de ce côté. Donc, si nous descendons, nous allons avoir une nouvelle durée, parce que chaque fois que nous descendons, nous allons augmenter leur vitesse. Donc, le minuteur de mouvement va en fait aller à une valeur plus petite. C' est ce que je fais ici. Réglage de la minuterie de mouvement sur une valeur plus petite. Allons chercher la nouvelle valeur. Ce sera le maximum de notre minuteur de mouvement actuel. Durée en millisecondes, moins 250. C' est vraiment notre défaut. C' est ce que je cherche. Mais si elle passe en dessous de 250, je vais le ramener à 250, parce que je ne veux pas les obtenir trop vite ! Besoin d'importer ce max à partir de la comparaison standard. Maintenant, nous pouvons aller self.move_timer est un timer.from_millis que nous venons de déterminer à partir d'une nouvelle durée. Seulement nous avons besoin de lancer ça à un u64. Seulement nous avons besoin de lancer ça à un u64. Et maintenant, nous pouvons enfin traverser chaque envahisseur. Donc, pour envahisseur dans l'armée de soi, itérer à travers elle muet et dire invader.y += 1 pour le déplacer vers le bas. Le déménagement était en fait la partie la plus facile de tout. Sinon, déplacez à gauche ou à droite. Donc, pour envahisseur dans l'auto armée, aussi iter_mut, invader.x est égal à, ok ici, nous devons aller invader.x comme un i32 pour qu'il puisse aller négatif. Plus self.direction - je devrais dire, self.direction est un i32 et donc pour les ajouter ensemble, ils doivent être du même type. Donc, ils vont autof.direction, puis jettent tout cela à un usize non signé. Donc ça va changer x de notre envahisseur, nous retournons vrai : jouer un son ou faux : ne pas jouer le son. Maintenant, nous avons notre logique pour les envahisseurs. Qu' en est-il de la capacité de les voir ? Alors allons-y et faisons impl Drawable for Invaders. Importer drawable. pour envahisseur, nous devons dessiner chaque envahisseur individuel. Donc, pour envahisseur dans self.army.iter (), passez par eux immuablement parce que nous n'avons pas besoin de les changer. Frame, envahisseur x envahisseur y, égales—nous allons faire quelque chose de spécial ici. Vous savez comment dans l'espace envahisseurs si vous avez déjà vu ce jeu classique, les extraterrestres agitent leurs tentacules ou les bras ou quoi que ce soit ou clignotent les yeux ? Nous allons faire quelque chose de similaire ! La moitié du temps nous allons faire un personnage et la moitié du temps nous allons faire un autre personnage. Donc, pendant qu'ils sont en place, on dirait qu'ils font une sorte de bras agitant ou quelque chose comme ça. Donc si nous avons besoin de prendre cette minuterie et de calculer dans quelle moitié nous sommes, donc nous allons prendre self.move_timer Donc si nous avons besoin de prendre cette minuterie et de calculer dans quelle moitié nous sommes, alors nous allons prendre self.move_timer le temps restant comme f32. Divisez cela par la durée du minuteur de déplacement en secondes, f32. Divisez cela par la durée du minuteur de déplacement en secondes, f32. Divisez cela par la durée du minuteur de déplacement en secondes, f32. Mettons cela sur des lignes séparées pour que nous puissions voir ça mieux. Ok, donc si nous prenons le temps à gauche, divisez-le par la durée totale de la minuterie, puis comparez-le à 0,5 , nous retournons un caractère. Alors faisons un « x » pour un état. Sinon, nous rendrons un autre personnage... et un point-virgule là-bas. Et si j'ai bien fait ça , alors on est prêts à brancher ça en main. Alors allons faire ça. Main.rs La première chose dont nous avons besoin est de quelques envahisseurs. Alors venons au sommet de la boucle de jeu - au-dessus de la boucle de jeu laisser les envahisseurs mut égaux Invaders። new () laisser les envahisseurs mut égaux Invaders። new () Ensuite, envahisseurs n'est pas contrôlé par l'entrée. Donc rien à faire là-bas. Il est certainement mis à jour par les minuteurs, cependant. Tout est guidé par des minuteurs. Donc, rappelez-vous, notre invaders.update () renvoie si nous devrions ou non jouer un son de mouvement. Donc, si invaders.update (delta), puis lecture audio déplacer son. Maintenant, nous avons la logique, mais nous ne les verrons toujours pas si nous les attirons dans notre cadre. Donc ici, je pourrais faire ça. Je pourrais aller envahirs.draw, cadre actuel mutable, et cela fonctionnerait. Mais je veux démontrer en utilisant des génériques avec un trait. Donc, je vais faire quelque chose de plus dur mais plus générique. Faisons un drawables, qui va être un vecteur de tout ce qui implémente le trait Drawable et en faire un vecteur d' une référence au joueur et une référence aux envahisseurs, et puis je peux dire pour chaque chose dessinable dans le vecteur drawables, que je vais consommer, drawable.draw (). La seule chose que je sais à propos de drawable est qu'il implémente le draw [méthode]. C' est donc littéralement la seule méthode que nous pouvons faire appel. Et de quoi a-t-il besoin ? Il a besoin d'un cadre courant mutable. Et puis ces deux lignes ne sont plus nécessaires. Donc, oui, c'est un peu plus compliqué. J' essaye juste de démontrer. Cela devient beaucoup plus puissant si vous l'utilisez avec une fonction ou quelque chose comme ça. Et nous obtenons le paiement. Si nous avons ajouté de plus en plus de choses Drawable. Tout ce que nous aurions à faire, c'est d'arriver à la fin de ce vecteur et de les ajouter au vecteur et nous n'aurions pas à changer d'autre logique. Bon, allons le faire fonctionner ! Bon, allons le faire fonctionner ! cargaison run [musical] « Bum budda bum ! » [ comédie musicale] « Bum budda bum ! » [ sons] chook, chook Est-ce qu'ils descendent ? Alors ils sont allés à droite, ils descendent. Vous pouvez les voir animer à mi-chemin de leur cycle. Et s'ils arrivent sur le côté gauche, allez ! Oui ! Ils sont descendus. D' accord. On peut encore bouger. On peut encore tirer. Le laser n'interagit pas encore. Mais c'est ce que nous allons corriger dans la section suivante. [ son] « Vous perdez » 34. Invaders partie 6 : Gagner et perdre: Envaders Partie 6 : Gagner et perdre Ce sera notre dernière section du projet des envahisseurs. Nous allons le faire pour gagner, nous pouvons perdre, en tuant les extraterrestres ou ils atteignent le bas de l'écran. Il y a beaucoup plus que nous pourrions faire. Vous savez, les petits extraterrestres qui traversent le sommet. Garder une trace des scores. Ajout de ces petits obstacles que vous pouvez cacher derrière et tirer à travers. En fait, si vous voulez venir essayer de contribuer soit par vous-même, dans votre propre petite fourchette, ou faire une demande de traction et essayer de la contribuer au projet open source. C' est très bien. Je suis totalement d'accord avec nous continuer à modifier cela sur GitHub. Alors allons-y. Revenons à notre envahisseur et faisons en sorte que nous puissions détecter si nous avons gagné en les tuant tous, perdu s'ils atteignent le bas de l'écran, et certainement faire en sorte que nous puissions les tuer du tout. perdu s'ils atteignent le bas de l'écran, et certainement faire pour que nous puissions les tuer du tout. Nous devons donc venir à leur logique ici et ajouter de nouvelles méthodes. Donc d'abord, « pub fn all_kills () » nous dira si nous avons tué toute l'armée envahissante. Ça va retourner un booléen. Celui-ci est vraiment simple. C' est juste self.army.is_empty () C'est juste self.army.is_empty () Super. Juste une expression de queue. Ensuite, faisons « pub fn reached_bottom () » Donc voici notre condition perdue. Aussi, un accès immuable à notre moi. Retourne un booléen, parce que c'est juste un oui/non. Et aussi une expression de queue, bien qu'un peu plus compliquée. Prenons notre self.army Essayons à travers elle immuablement. Mappez tous nos envahisseurs à leur position y. Donc, nous regardons jusqu'où ils sont venus. On prend le maximum, car à mesure que Y augmente, ils descendent à l'écran. C' est faillible. Donc nous devons faire déballer, ou nous allons supposer qu'ils sont en haut de l'écran. ( Vraiment, ils sont probablement tous morts.) Et puis si cela est supérieur ou égal à NUM_ROMS - 1, alors nous avons atteint le bas. Et enfin, nous avons besoin d'un moyen de tuer un envahisseur. Donc « pub fn kill_invader_at () ». Donc, voici une tentative de tuer un envahisseur. Donc, un accès mutable à notre auto au cas où nous le tuions. On a besoin du X, du Y, vous pouvez probablement deviner que ces X et Y vont provenir de coups de feu. Et cela va retourner booléen pour que nous sachions : Avons-nous réellement tué un envahisseur ? Devrions-nous jouer un son d'explosion ? D' accord. Donc, ce que nous allons faire, c'est que nous allons dire « si nous laissons certains (idx) » être notre auto. Army—Je veux faire cette verticale donc nous le gardons à l'écran — iter, position Donc la position prend une fermeture, encore une fois, donc notre envahisseur — pour chaque envahisseur — nous allons appliquer Eh bien, ff l'envahisseur x est x et l'envahisseur y est y, alors il nous donnera notre index afin que nous puissions faire self.army.remove (idx) self.army.remove (idx) Il y a une méthode réelle sur un itérateur qui peut faire tout cela en un appel de méthode. Une sorte d'appel de méthode fonctionnelle, au lieu de vérifier l'index et, si nous le trouvons, puis de le supprimer. Une sorte d'appel de méthode fonctionnelle, au lieu de vérifier l'index et, si nous le trouvons, puis de le supprimer. Mais au moment où j'enregistre ça, c'est instable. Donc ce n'est qu'une rouille nocturne, pas dans la rouille stable. Donc je m'en tiens aux vieux trucs ici. Et puis le vrai ou le faux est notre retour. Que nous ayons retiré quelqu'un de l'armée ou non. Si on a tué un envahisseur, pour qu'on puisse jouer ce son. Maintenant, passons au module du lecteur et branchons ça. Donc, Player va avoir besoin d'un « pub fn detect_hits » Il a besoin d'un accès mutable à lui-même et il a également besoin d'une référence mutable à un Invaders. Donc, il peut essayer d'appeler cette méthode que nous venons de créer. Assurez-vous d'importer cela. Ça va retourner un booléen parce que nous devons passer par ce bruit d'explosion si ça arrive. Alors, on a touché quelque chose ? Et on va dire, d'abord, non, on ne pense pas. Et nous rendrons ça à la fin. Donc il y a notre réponse au milieu. Nous dirons, eh bien, pour chaque tir que nous avons, peut être nul, peut être jusqu'à deux. Si vous aviez utilisé un nombre différent, cela pourrait être plus : il faut les parcourir de façon muetable. Et puis si le tir n'explose pas parce qu'un tir explosant n'arrive pas à frapper un autre extraterrestre. Ce serait idiot. Donc, si invaders.kill_invader_at. Voilà notre tentative. shot.x shot.y Alors, oui, on a frappé quelque chose. Alors, oui, nous avons frappé quelque chose, alors mis cela à vrai et aussi nous devons dire notre tir : vous êtes maintenant en train d'exploser. Maintenant, nous allons afficher ce personnage d'astérisque, qui est notre grand personnage d'explosion. Maintenant que nous avons branché ça avec le joueur, revenons à la main et le brancher dans la boucle principale. Alors, revenez à la main. On a déjà des envahisseurs. On a déjà un joueur. Donc, ici, dans nos mises à jour, ajoutons un : si le joueur — demandez-lui de détecter les coups avec tous ses coups, il a besoin d'un envahisseur mutable afin qu'il puisse vérifier s'il frappe quelque chose — puis jouez. exploser ! Donc ce serait notre son « kaplew ». Maintenant, nous avons la capacité de tuer un extraterrestre. Nous sommes donc prêts à vérifier nos conditions de victoire ou de perte. Alors passons sous notre tirage au sort et rendu parce que nous voulons que cela se produise d'abord avant de gagner ou de perdre. Donc, en dessous de cela, ajoutons une section de victoire ou de perte. Faites défiler ceci un peu plus pour que vous puissiez voir. Si les envahisseurs étaient tous tués. Voici notre condition de victoire, parce que nous voulons toujours, vous savez, égalité va au joueur. audio.play (« gagner ») Grande. Maintenant, nous devons sortir de notre boucle de jeu parce que nous n'avons pas de menus ou quelque chose comme ça. Nous sortons juste et puis notre condition perdue si l'envahisseur a atteint le fond. Alors nous devons jouer à perdre. Et cette fois, vous avez vraiment perdu parce qu'ils sont arrivés au fond. Tu as tué, sans doute. Alors sortez de la boucle du jeu, encore une fois. Maintenant, nous avons toute notre logique. Allons voir si ça marche. Est-ce que je l'ai fait correctement ? cargaison run [musicalement] « Bum budda bum ! » Tirez. Oh, manqué. [sons laser et explosion] Ça marche. [sons laser et explosion] [plus de sons laser et explosion] OK, alors vérifions notre victoire et perdons les conditions. Alors je vais démissionner. [perdre son] Je vais revenir à notre code. Et passons aux envahisseurs et revenons ici à la ligne 29 et ajoutons un peu plus de logique. Donc, permettons seulement quelques envahisseurs, donc - et x est exactement deux. Allons essayer ça. Ah, beaucoup plus facile à gagner. Ok, essayons ça. [ sons laser et explosion] [son gagnant] « Vous gagnez. » Hé, on y va ! Voilà notre condition de victoire. Super. Alors maintenant, on peut y retourner, annuler ça. Vérifions notre condition perdue. S' ils bougent très vite, ça va être dur. Alors réglons ça à notre minimum de temps ici, 250. Cargo run. [son de démarrage] Vache sacrée ! D' accord. Allez ! On peut le faire ! [ sons laser et explosion] Maintenons ça. [ sons laser et explosion] Pew, Pew. [ sons laser et explosion] Maintenir en place a cessé de fonctionner. Je ne sais pas ce qui s'est passé là-bas. [sons laser et explosion] Oh la situation est désastreuse. [sons laser et explosion] [perte de son] « Vous perdez » Ok, notre état de perte fonctionne aussi. Maintenant, nous avons tout testé et c'est tout ! C' est le projet ! Va annuler cette dernière chose, et on a notre état final pour le projet. Maintenant, si vous voulez aller plus loin, s'il vous plaît faire ! Expérimentez ça avec ça. Ajoutez d'autres fonctionnalités. Gardez une trace d'un score. Modifiez les dimensions du champ de jeu. Si vous voulez être vraiment fantaisie, vous pouvez refaire une partie de la logique de base — au lieu d'avoir un seul caractère, un vaisseau et un seul caractère extraterrestre, vous pourriez essayer de les rendre multi-caractères. au lieu d'avoir un seul caractère, navire et alien à un seul caractère, vous pourriez essayer de les rendre multi-caractères. Vous devrez refaire beaucoup de logique si vous allez dans ce sens ! Quoi qu'il en soit, si vous faites une partie de cela et que vous souhaitez la reverser au projet, s'il vous plaît allez-y et faites une demande de traction. Je suis bien d'altérer ce projet. Vous pouvez toujours revenir à ces six étapes dans ces vidéos en consultant les tags parties 1 à partie 6. Vous pouvez toujours revenir à ces six étapes dans ces vidéos en consultant les tags parties 1 à partie 6. Vraiment facile. Juste « git checkout » ce nom de balise. Juste « git checkout » ce nom de balise. Merci beaucoup ! J' espère que vous avez apprécié ce projet.፦) 35. Le mot de la fin: On a joué avec tous les jouets de la boîte à outils. Maintenant, je veux savoir : Que pensez-vous de ce cours ? Qu' est-ce que tu aimais ? Qu' est-ce que tu n'aimais pas ? Qu' est-ce que tu changerais ? Qu' est-ce que tu ajouterais ? J' ai vraiment aimé faire ce cours et j'espère que vous avez aimé le suivre ! Que votre voyage avec Rust soit une grande aventure !