Rost - Kurzanleitung

Rust ist eine Programmiersprache auf Systemebene, die von Graydon Hoare entwickelt wurde. Mozilla Labs erwarb das Programm später.

Anwendung v / s Systemprogrammiersprachen

Anwendungsprogrammiersprachen wie Java / C # werden zum Erstellen von Software verwendet, die dem Benutzer Dienste direkt bereitstellt. Sie helfen uns beim Erstellen von Geschäftsanwendungen wie Tabellenkalkulationen, Textverarbeitungsprogrammen, Webanwendungen oder mobilen Anwendungen.

Systemprogrammiersprachen wie C / C ++ werden zum Erstellen von Software und Softwareplattformen verwendet. Sie können zum Erstellen von Betriebssystemen, Game-Engines, Compilern usw. verwendet werden. Diese Programmiersprachen erfordern ein hohes Maß an Hardware-Interaktion.

Systeme und Anwendungsprogrammiersprachen stehen vor zwei Hauptproblemen:

  • Es ist schwierig, sicheren Code zu schreiben.
  • Es ist schwierig, Multithread-Code zu schreiben.

Warum Rost?

Rust konzentriert sich auf drei Ziele -

  • Safety
  • Speed
  • Concurrency

Die Sprache wurde entwickelt, um auf einfache Weise hochzuverlässige und schnelle Software zu entwickeln. Mit Rust können übergeordnete Programme in hardwarespezifische Programme geschrieben werden.

Performance

Die Programmiersprache Rust verfügt nicht über einen Garbage Collector (GC). Dies verbessert die Leistung zur Laufzeit.

Speichersicherheit beim Kompilieren

Mit Rust erstellte Software ist vor Speicherproblemen wie baumelnden Zeigern, Pufferüberläufen und Speicherlecks geschützt.

Multithread-Anwendungen

Die Eigentums- und Speichersicherheitsregeln von Rust bieten Parallelität ohne Datenrennen.

Unterstützung für Web Assembly (WASM)

Web Assembly hilft dabei, rechenintensive Algorithmen im Browser, auf eingebetteten Geräten oder anderswo auszuführen. Es läuft mit der Geschwindigkeit von nativem Code. Rust kann für eine schnelle und zuverlässige Ausführung in Web Assembly kompiliert werden.

Die Installation von Rust wird durch erleichtert rustup, ein konsolenbasiertes Tool zum Verwalten von Rust-Versionen und zugehörigen Tools.

Installation unter Windows

Lassen Sie uns lernen, wie Sie RUST unter Windows installieren.

  • Die Installation von Visual Studio 2013 oder höher mit C ++ - Tools ist obligatorisch, um das Rust-Programm unter Windows auszuführen. Laden Sie zunächst Visual Studio von hier herunter. VS 2013 Express

  • Herunterladen und installieren rustup Werkzeug für Windows. rustup-init.exesteht hier zum Download zur Verfügung - Rust Lang

  • Doppelklick rustup-init.exeDatei. Nach dem Klicken wird der folgende Bildschirm angezeigt.

  • Drücken Sie die Eingabetaste für die Standardinstallation. Nach Abschluss der Installation wird der folgende Bildschirm angezeigt.

  • Auf dem Installationsbildschirm wird deutlich, dass Rust-bezogene Dateien im Ordner gespeichert sind -

    C: \ Benutzer \ {PC} \. Fracht \ bin

Der Inhalt des Ordners ist -

cargo-fmt.exe
cargo.exe
rls.exe
rust-gdb.exe
rust-lldb.exe
rustc.exe // this is the compiler for rust
rustdoc.exe
rustfmt.exe
rustup.exe
  • Cargoist der Paketmanager für Rust. Um zu überprüfen, obcargo Wenn installiert ist, führen Sie den folgenden Befehl aus:

C:\Users\Admin>cargo -V
cargo 1.29.0 (524a578d7 2018-08-05)
  • Der Compiler für Rust ist rustc. Führen Sie den folgenden Befehl aus, um die Compilerversion zu überprüfen:

C:\Users\Admin>cargo -V
cargo 1.29.0 (524a578d7 2018-08-05)

Installation unter Linux / Mac

Installieren rustup Öffnen Sie unter Linux oder MacOS ein Terminal und geben Sie den folgenden Befehl ein.

$ curl https://sh.rustup.rs -sSf | sh

Der Befehl lädt ein Skript herunter und startet die Installation von rustupTool, das die neueste stabile Version von Rust installiert. Möglicherweise werden Sie zur Eingabe Ihres Kennworts aufgefordert. Wenn die Installation erfolgreich ist, wird die folgende Zeile angezeigt:

Rust is installed now. Great!

Das Installationsskript fügt Ihrem Systempfad nach Ihrer nächsten Anmeldung automatisch Rust hinzu. Führen Sie den folgenden Befehl in Ihrer Shell aus, um Rust manuell zu Ihrem Systempfad hinzuzufügen, um Rust sofort zu verwenden, anstatt Ihr Terminal neu zu starten.

$ source $HOME/.cargo/env

Alternativ können Sie Ihrem ~ / .bash_profile die folgende Zeile hinzufügen:

$ export PATH="$HOME/.cargo/bin:$PATH"

NOTE - Wenn Sie versuchen, ein Rust-Programm zu kompilieren und Fehler erhalten, die darauf hinweisen, dass ein Linker nicht ausgeführt werden konnte, bedeutet dies, dass kein Linker auf Ihrem System installiert ist und Sie einen manuell installieren müssen.

Verwenden von Tutorials Point Coding Ground für RUST

Eine Read-Evaluate-Print-Schleife (REPL) ist eine einfach zu verwendende interaktive Shell zum Kompilieren und Ausführen von Computerprogrammen. Wenn Sie Rust-Programme online im Browser kompilieren und ausführen möchten, verwenden Sie Tutorialspoint Coding Ground .

In diesem Kapitel wird die grundlegende Syntax der Rust-Sprache anhand von a erläutert HelloWorld Beispiel.

  • Ein ... kreieren HelloWorld-App Ordner und navigieren Sie zu diesem Ordner auf dem Terminal

C:\Users\Admin>mkdir HelloWorld-App
C:\Users\Admin>cd HelloWorld-App
C:\Users\Admin\HelloWorld-App>
  • Führen Sie den folgenden Befehl aus, um eine Rust-Datei zu erstellen:

C:\Users\Admin\HelloWorld-App>notepad Hello.rs

Rust-Programmdateien haben die Erweiterung .rs. Der obige Befehl erstellt eine leere DateiHello.rsund öffnet es im NOTEpad. Fügen Sie den folgenden Code zu dieser Datei hinzu -

fn
main(){
   println!("Rust says Hello to TutorialsPoint !!");
}

Das obige Programm definiert eine Funktion main fn main () . Das Schlüsselwort fn wird verwendet, um eine Funktion zu definieren. Die main () ist eine vordefinierte Funktion , die an das Programm als Einstiegspunkt wirkt. println! ist ein vordefiniertes Makro in Rust. Es wird verwendet, um eine Zeichenfolge (hier Hallo) an die Konsole zu drucken. Makroaufrufe sind immer mit einem Ausrufezeichen gekennzeichnet - ! .

  • Kompilieren Sie die Hello.rs Datei mit rustc.

C:\Users\Admin\HelloWorld-App>rustc Hello.rs

Nach erfolgreicher Kompilierung des Programms wird eine ausführbare Datei ( file_name.exe ) generiert. Führen Sie den folgenden Befehl aus, um zu überprüfen, ob die EXE- Datei generiert wurde.

C:\Users\Admin\HelloWorld-App>dir
//lists the files in folder
Hello.exe
Hello.pdb
Hello.rs
  • Führen Sie die Datei Hello.exe aus und überprüfen Sie die Ausgabe.

Was ist ein Makro?

Rust bietet ein leistungsstarkes Makrosystem, das Metaprogrammierung ermöglicht. Wie Sie im vorherigen Beispiel gesehen haben, sehen Makros wie Funktionen aus, außer dass ihr Name mit einem Knall (!) Endet. Anstatt jedoch einen Funktionsaufruf zu generieren, werden Makros zu Quellcode erweitert, der mit dem Rest des Programms kompiliert wird. Daher bieten sie einem Programm im Gegensatz zu Funktionen mehr Laufzeitfunktionen. Makros sind eine erweiterte Version von Funktionen.

Mit dem Druck! Makro - Syntax

println!(); // prints just a newline
println!("hello ");//prints hello
println!("format {} arguments", "some"); //prints format some arguments

Kommentare in Rust

Kommentare sind eine Möglichkeit, die Lesbarkeit eines Programms zu verbessern. Kommentare können verwendet werden, um zusätzliche Informationen zu einem Programm wie den Autor des Codes, Hinweise zu einer Funktion / einem Konstrukt usw. aufzunehmen. Der Compiler ignoriert Kommentare.

Rust unterstützt die folgenden Arten von Kommentaren:

  • Einzeilige Kommentare (//) - Jeder Text zwischen einem // und dem Ende einer Zeile wird als Kommentar behandelt

  • Mehrzeilige Kommentare (/ * * /) - Diese Kommentare können mehrere Zeilen umfassen.

Beispiel

//this is single line comment

/* This is a
   Multi-line comment
*/

Online ausführen

Rostprogramme können online über Tutorialspoint Coding Ground ausgeführt werden . Schreiben Sie das HelloWorld- Programm in die Registerkarte Editor und klicken Sie auf Ausführen, um das Ergebnis anzuzeigen.

Das Typsystem repräsentiert die verschiedenen Arten von Werten, die von der Sprache unterstützt werden. Das Typsystem überprüft die Gültigkeit der angegebenen Werte, bevor sie vom Programm gespeichert oder bearbeitet werden. Dies stellt sicher, dass sich der Code wie erwartet verhält. Das Typsystem ermöglicht außerdem umfassendere Code-Hinweise und eine automatisierte Dokumentation.

Rust ist eine statisch typisierte Sprache. Jeder Wert in Rust ist von einem bestimmten Datentyp. Der Compiler kann anhand des ihm zugewiesenen Werts automatisch auf den Datentyp der Variablen schließen.

Deklarieren Sie eine Variable

Verwenden Sie die let Schlüsselwort zum Deklarieren einer Variablen.

fn main() {
   let company_string = "TutorialsPoint";  // string type
   let rating_float = 4.5;                 // float type
   let is_growing_boolean = true;          // boolean type
   let icon_char = '♥';                    //unicode character type

   println!("company name is:{}",company_string);
   println!("company rating on 5 is:{}",rating_float);
   println!("company is growing :{}",is_growing_boolean);
   println!("company icon is:{}",icon_char);
}

Im obigen Beispiel wird der Datentyp der Variablen aus den ihnen zugewiesenen Werten abgeleitet. Beispielsweise weist Rust der Variablen company_string einen String-Datentyp zu , float_float usw. den float-Datentyp .

Der Druck! Makro nimmt zwei Argumente -

  • Eine spezielle Syntax {} , die den Platzhalter darstellt
  • Der Variablenname oder eine Konstante

Der Platzhalter wird durch den Wert der Variablen ersetzt

Die Ausgabe des obigen Code-Snippets lautet -

company name is: TutorialsPoint
company rating on 5 is:4.5
company is growing: true
company icon is: ♥

Skalartypen

Ein Skalartyp repräsentiert einen einzelnen Wert. Zum Beispiel 10,3.14, 'c'. Rust hat vier primäre Skalartypen.

  • Integer
  • Floating-point
  • Booleans
  • Characters

Wir werden in den folgenden Abschnitten mehr über jeden Typ erfahren.

Ganze Zahl

Eine Ganzzahl ist eine Zahl ohne Bruchkomponente. Einfach ausgedrückt wird der ganzzahlige Datentyp verwendet, um ganze Zahlen darzustellen.

Ganzzahlen können weiter als vorzeichenbehaftet und vorzeichenlos klassifiziert werden. Vorzeichenbehaftete Ganzzahlen können sowohl negative als auch positive Werte speichern. Ganzzahlen ohne Vorzeichen können nur positive Werte speichern. Eine detaillierte Beschreibung, ob ganzzahlige Typen unten angegeben sind -

Sr.Nr. Größe Unterzeichnet Ohne Vorzeichen
1 8 Bit i8 u8
2 16 Bit i16 u16
3 32 Bit i32 u32
4 64 Bit i64 u64
5 128 Bit i128 u128
6 Bogen isize nutzen

Die Größe einer Ganzzahl kann arch sein . Dies bedeutet, dass die Größe des Datentyps von der Architektur der Maschine abgeleitet wird. Eine Ganzzahl, deren Größe arch ist, beträgt auf einem x86-Computer 32 Bit und auf einem x64-Computer 64 Bit. Eine Bogen-Ganzzahl wird hauptsächlich beim Indizieren einer Sammlung verwendet.

Illustration

fn main() {
   let result = 10;    // i32 by default
   let age:u32 = 20;
   let sum:i32 = 5-15;
   let mark:isize = 10;
   let count:usize = 30;
   println!("result value is {}",result);
   println!("sum is {} and age is {}",sum,age);
   println!("mark is {} and count is {}",mark,count);
}

Die Ausgabe erfolgt wie folgt:

result value is 10
sum is -10 and age is 20
mark is 10 and count is 30

Der obige Code gibt einen Kompilierungsfehler zurück, wenn Sie den Wert für age durch einen Gleitkommawert ersetzen .

Integer Range

Jede vorzeichenbehaftete Variante kann Zahlen von - (2 ^ (n-1) bis 2 ^ (n-1) -1 speichern, wobei n die Anzahl der von der Variante verwendeten Bits ist. Beispielsweise kann i8 Zahlen von - (2 ^) speichern 7) bis 2 ^ 7 -1 - hier haben wir n durch 8 ersetzt.

Jede vorzeichenlose Variante kann Zahlen von 0 bis (2 ^ n) -1 speichern . Zum Beispiel kann u8 Zahlen von 0 bis 2 ^ 7 speichern , was 0 bis 255 entspricht.

Ganzzahliger Überlauf

Ein ganzzahliger Überlauf tritt auf, wenn der einer ganzzahligen Variablen zugewiesene Wert den für den Datentyp definierten Rust-Bereich überschreitet. Lassen Sie uns dies anhand eines Beispiels verstehen -

fn main() {
   let age:u8 = 255;

   // 0 to 255 only allowed for u8
   let weight:u8 = 256;   //overflow value is 0
   let height:u8 = 257;   //overflow value is 1
   let score:u8 = 258;    //overflow value is 2

   println!("age is {} ",age);
   println!("weight is {}",weight);
   println!("height is {}",height);
   println!("score is {}",score);
}

Der gültige Bereich der vorzeichenlosen u8-Variablen liegt zwischen 0 und 255. Im obigen Beispiel werden den Variablen Werte größer als 255 zugewiesen (Obergrenze für eine ganzzahlige Variable in Rust). Bei der Ausführung gibt der obige Code eine Warnung zurück -warning − literal out of range for u8für Gewichts-, Größen- und Bewertungsvariablen. Die Überlaufwerte nach 255 beginnen bei 0, 1, 2 usw. Die endgültige Ausgabe ohne Warnung ist wie folgt:

age is 255
weight is 0
height is 1
score is 2

Schweben

Der Float-Datentyp in Rust kann als klassifiziert werden f32 und f64. Der Typ f32 ist ein Float mit einfacher Genauigkeit, und f64 hat eine doppelte Genauigkeit. Der Standardtyp ist f64. Betrachten Sie das folgende Beispiel, um mehr über den Float-Datentyp zu erfahren.

fn main() {
   let result = 10.00;        //f64 by default
   let interest:f32 = 8.35;
   let cost:f64 = 15000.600;  //double precision
   
   println!("result value is {}",result);
   println!("interest is {}",interest);
   println!("cost is {}",cost);
}

Die Ausgabe erfolgt wie unten gezeigt -

interest is 8.35
cost is 15000.6

Automatisches Gießen

Automatisches Gießen ist in Rust nicht zulässig. Betrachten Sie das folgende Code-Snippet. Der Float-Variablen wird ein ganzzahliger Wert zugewieseninterest.

fn main() {
   let interest:f32 = 8;   // integer assigned to float variable
   println!("interest is {}",interest);
}

Der Compiler löst a mismatched types error wie unten angegeben.

error[E0308]: mismatched types
   --> main.rs:2:22
   |
 2 | let interest:f32=8;
   |    ^ expected f32, found integral variable
   |
   = note: expected type `f32`
      found type `{integer}`
error: aborting due to previous error(s)

Nummernseparator

Zur einfachen Lesbarkeit großer Zahlen können wir einen visuellen Trennzeichen _ Unterstrich verwenden, um Ziffern zu trennen. Das heißt, 50.000 können als 50_000 geschrieben werden. Dies ist im folgenden Beispiel dargestellt.

fn main() {
   let float_with_separator = 11_000.555_001;
   println!("float value {}",float_with_separator);
   
   let int_with_separator = 50_000;
   println!("int value {}",int_with_separator);
}

Die Ausgabe ist unten angegeben -

float value 11000.555001
int value 50000

Boolescher Wert

Boolesche Typen haben zwei mögliche Werte - wahr oder falsch . Verwenden Sie diebool Schlüsselwort zum Deklarieren einer booleschen Variablen.

Illustration

fn main() {
   let isfun:bool = true;
   println!("Is Rust Programming Fun ? {}",isfun);
}

Die Ausgabe des obigen Codes lautet -

Is Rust Programming Fun ? true

Charakter

Der Zeichendatentyp in Rust unterstützt Zahlen, Alphabete, Unicode und Sonderzeichen. Verwenden Sie diecharSchlüsselwort zum Deklarieren einer Variablen vom Zeichendatentyp. Der char-Typ von Rust repräsentiert einen Unicode-Skalarwert, was bedeutet, dass er viel mehr als nur ASCII darstellen kann. Unicode-Skalarwerte reichen vonU+0000 zu U+D7FF und U+E000 zu U+10FFFF inklusive.

Betrachten wir ein Beispiel, um mehr über den Zeichendatentyp zu erfahren.

fn main() {
   let special_character = '@'; //default
   let alphabet:char = 'A';
   let emoji:char = '';
   
   println!("special character is {}",special_character);
   println!("alphabet is {}",alphabet);
   println!("emoji is {}",emoji);
}

Die Ausgabe des obigen Codes lautet -

special character is @
alphabet is A
emoji is

Eine Variable ist ein benannter Speicher, den Programme bearbeiten können. Einfach ausgedrückt, eine Variable hilft Programmen beim Speichern von Werten. Variablen in Rust sind einem bestimmten Datentyp zugeordnet. Der Datentyp bestimmt die Größe und das Layout des Speichers der Variablen, den Wertebereich, der in diesem Speicher gespeichert werden kann, und die Anzahl der Operationen, die für die Variable ausgeführt werden können.

Regeln für die Benennung einer Variablen

In diesem Abschnitt lernen wir die verschiedenen Regeln für die Benennung einer Variablen kennen.

  • Der Name einer Variablen kann aus Buchstaben, Ziffern und dem Unterstrich bestehen.

  • Es muss entweder mit einem Buchstaben oder einem Unterstrich beginnen.

  • Groß- und Kleinbuchstaben unterscheiden sich, da bei Rust zwischen Groß- und Kleinschreibung unterschieden wird.

Syntax

Der Datentyp ist optional, wenn eine Variable in Rust deklariert wird. Der Datentyp wird aus dem der Variablen zugewiesenen Wert abgeleitet.

Die Syntax zum Deklarieren einer Variablen ist unten angegeben.

let variable_name = value;            // no type specified
let variable_name:dataType = value;   //type specified

Illustration

fn main() {
   let fees = 25_000;
   let salary:f64 = 35_000.00;
   println!("fees is {} and salary is {}",fees,salary);
}

Die Ausgabe des obigen Codes wird sein fees is 25000 and salary is 35000.

Unveränderlich

Standardmäßig sind Variablen unveränderlich - schreibgeschützt in Rust. Mit anderen Worten, der Wert der Variablen kann nicht geändert werden, sobald ein Wert an einen Variablennamen gebunden ist.

Lassen Sie uns dies anhand eines Beispiels verstehen.

fn main() {
   let fees = 25_000;
   println!("fees is {} ",fees);
   fees = 35_000;
   println!("fees changed is {}",fees);
}

Die Ausgabe erfolgt wie unten gezeigt -

error[E0384]: re-assignment of immutable variable `fees`
 --> main.rs:6:3
   |
 3 | let fees = 25_000;
   | ---- first assignment to `fees`
...
 6 | fees=35_000;
   | ^^^^^^^^^^^ re-assignment of immutable variable

error: aborting due to previous error(s)

Die Fehlermeldung gibt die Fehlerursache an. Sie können unveränderlichen variablen Gebühren nicht zweimal Werte zuweisen. Dies ist eine der vielen Möglichkeiten, mit denen Rust Programmierern das Schreiben von Code ermöglicht und die Sicherheit und die einfache Parallelität nutzt.

Veränderlich

Variablen sind standardmäßig unveränderlich. Stellen Sie dem Variablennamen das Präfix vormutSchlüsselwort, um es veränderlich zu machen. Der Wert einer veränderlichen Variablen kann geändert werden.

Die Syntax zum Deklarieren einer veränderlichen Variablen lautet wie folgt:

let mut variable_name = value;
let mut variable_name:dataType = value;
Let us understand this with an example

fn main() {
   let mut fees:i32 = 25_000;
   println!("fees is {} ",fees);
   fees = 35_000;
   println!("fees changed is {}",fees);
}

Die Ausgabe des Snippets ist unten angegeben -

fees is 25000
fees changed is 35000

Konstanten repräsentieren Werte, die nicht geändert werden können. Wenn Sie eine Konstante deklarieren, ändert sich ihr Wert auf keinen Fall. Das Schlüsselwort für die Verwendung von Konstanten lautetconst. Konstanten müssen explizit eingegeben werden. Es folgt die Syntax zum Deklarieren einer Konstante.

const VARIABLE_NAME:dataType = value;

Rust Constant Naming Convention

Die Namenskonvention für Konstanten ähnelt der von Variablen. Alle Zeichen in einem konstanten Namen werden normalerweise in Großbuchstaben geschrieben. Im Gegensatz zur Deklaration von Variablen ist dielet Das Schlüsselwort wird nicht verwendet, um eine Konstante zu deklarieren.

Wir haben im folgenden Beispiel Konstanten in Rust verwendet -

fn main() {
   const USER_LIMIT:i32 = 100;    // Declare a integer constant
   const PI:f32 = 3.14;           //Declare a float constant

   println!("user limit is {}",USER_LIMIT);  //Display value of the constant
   println!("pi value is {}",PI);            //Display value of the constant
}

Konstanten v / s Variablen

In diesem Abschnitt lernen wir die Unterscheidungsfaktoren zwischen Konstanten und Variablen kennen.

  • Konstanten werden mit dem deklariert const Schlüsselwort, während Variablen mit dem deklariert werden let Stichwort.

  • Eine Variablendeklaration kann optional einen Datentyp haben, während eine konstante Deklaration den Datentyp angeben muss. Dies bedeutet, dass const USER_LIMIT = 100 zu einem Fehler führt.

  • Eine Variable, die mit dem deklariert wurde letDas Schlüsselwort ist standardmäßig unveränderlich. Sie haben jedoch die Möglichkeit, es mit dem zu mutierenmutStichwort. Konstanten sind unveränderlich.

  • Konstanten können nur auf einen konstanten Ausdruck gesetzt werden und nicht auf das Ergebnis eines Funktionsaufrufs oder eines anderen Werts, der zur Laufzeit berechnet wird.

  • Konstanten können in jedem Bereich deklariert werden, einschließlich des globalen Bereichs. Dies macht sie nützlich für Werte, über die viele Teile des Codes Bescheid wissen müssen.

Abschattung von Variablen und Konstanten

Mit Rust können Programmierer gleichnamige Variablen deklarieren. In diesem Fall überschreibt die neue Variable die vorherige Variable.

Lassen Sie uns dies anhand eines Beispiels verstehen.

fn main() {
   let salary = 100.00;
   let salary = 1.50 ; 
   // reads first salary
   println!("The value of salary is :{}",salary);
}

Der obige Code deklariert zwei Variablen mit dem Namen Gehalt. Der ersten Deklaration wird 100,00 zugewiesen, während der zweiten Deklaration der Wert 1,50 zugewiesen wird. Die zweite Variable schattiert oder verbirgt die erste Variable, während die Ausgabe angezeigt wird.

Ausgabe

The value of salary is :1.50

Rust unterstützt beim Shadowing Variablen mit unterschiedlichen Datentypen.

Betrachten Sie das folgende Beispiel.

Der Code deklariert zwei Variablen mit dem Namen uname. Der ersten Deklaration wird ein Zeichenfolgenwert zugewiesen, während der zweiten Deklaration eine Ganzzahl zugewiesen wird. Die len-Funktion gibt die Gesamtzahl der Zeichen in einem Zeichenfolgenwert zurück.

fn main() {
   let uname = "Mohtashim";
   let uname = uname.len();
   println!("name changed to integer : {}",uname);
}

Ausgabe

name changed to integer: 9

Im Gegensatz zu Variablen können Konstanten nicht schattiert werden. Wenn Variablen im obigen Programm durch Konstanten ersetzt werden, gibt der Compiler einen Fehler aus.

fn main() {
   const NAME:&str = "Mohtashim";
   const NAME:usize = NAME.len(); 
   //Error : `NAME` already defined
   println!("name changed to integer : {}",NAME);
}

Der String-Datentyp in Rust kann wie folgt klassifiziert werden:

  • String Literal(&str)

  • String-Objekt(String)

String Literal

String-Literale (& str) werden verwendet, wenn der Wert eines Strings zur Kompilierungszeit bekannt ist. String-Literale sind eine Reihe von Zeichen, die in einer Variablen fest codiert sind. Lassen Sie zum Beispiel company = "Tutorials Point" . String-Literale finden Sie im Modul std :: str. String-Literale werden auch als String-Slices bezeichnet.

Im folgenden Beispiel werden zwei Zeichenfolgenliterale deklariert - Firma und Standort .

fn main() {
   let company:&str="TutorialsPoint";
   let location:&str = "Hyderabad";
   println!("company is : {} location :{}",company,location);
}

String-Literale sind standardmäßig statisch. Dies bedeutet, dass String-Literale garantiert für die Dauer des gesamten Programms gültig sind. Wir können die Variable auch explizit als statisch angeben, wie unten gezeigt -

fn main() {
   let company:&'static str = "TutorialsPoint";
   let location:&'static str = "Hyderabad";
   println!("company is : {} location :{}",company,location);
}

Das obige Programm erzeugt die folgende Ausgabe -

company is : TutorialsPoint location :Hyderabad

String-Objekt

Der Objekttyp String wird in der Standardbibliothek bereitgestellt. Im Gegensatz zum Zeichenfolgenliteral ist der Zeichenfolgenobjekttyp kein Teil der Kernsprache. Es ist als öffentliche Struktur in der Standardbibliothek pub struct String definiert . String ist eine erweiterbare Sammlung. Es ist veränderlich und UTF-8-codiert. DasStringDer Objekttyp kann verwendet werden, um Zeichenfolgenwerte darzustellen, die zur Laufzeit bereitgestellt werden. Das String-Objekt wird im Heap zugewiesen.

Syntax

Um ein String-Objekt zu erstellen, können Sie eine der folgenden Syntax verwenden:

String::new()

Die obige Syntax erstellt eine leere Zeichenfolge

String::from()

Dadurch wird eine Zeichenfolge mit einem Standardwert erstellt, der als Parameter an die übergeben wird from() Methode.

Das folgende Beispiel zeigt die Verwendung eines String-Objekts.

fn main(){
   let empty_string = String::new();
   println!("length is {}",empty_string.len());

   let content_string = String::from("TutorialsPoint");
   println!("length is {}",content_string.len());
}

Im obigen Beispiel werden zwei Zeichenfolgen erstellt - ein leeres Zeichenfolgenobjekt mit der neuen Methode und ein Zeichenfolgenobjekt aus dem Zeichenfolgenliteral mit der from- Methode.

Die Ausgabe ist wie unten gezeigt -

length is 0
length is 14

Allgemeine Methoden - String-Objekt

Sr.Nr. Methode Unterschrift Beschreibung
1 Neu() pub const fn new () → String Erstellt einen neuen leeren String.
2 to_string () fn to_string (& self) → String Konvertiert den angegebenen Wert in einen String.
3 ersetzen() pub fn ersetze <'a, P> (&' a self, von: P, bis: & str) → String Ersetzt alle Übereinstimmungen eines Musters durch eine andere Zeichenfolge.
4 as_str () pub fn as_str (& self) → & str Extrahiert ein String-Slice, das den gesamten String enthält.
5 drücken() pub fn push (& mut self, ch: char) Hängt das angegebene Zeichen an das Ende dieser Zeichenfolge an.
6 push_str () pub fn push_str (& mut self, string: & str) Hängt ein bestimmtes String-Slice an das Ende dieses Strings an.
7 len () pub fn len (& self) → usize Gibt die Länge dieses Strings in Bytes zurück.
8 trimmen() pub fn trim (& self) → & str Gibt ein String-Slice zurück, bei dem führende und nachfolgende Leerzeichen entfernt sind.
9 split_whitespace () pub fn split_whitespace (& self) → SplitWhitespace Teilt ein String-Slice nach Leerzeichen und gibt einen Iterator zurück.
10 Teilt() pub fn split <'a, P> (&' a self, pat: P) → Split <'a, P>, wobei P ein Muster ist, kann & str, char oder ein Abschluss sein, der die Aufteilung bestimmt. Gibt einen Iterator über Teilzeichenfolgen dieses String-Slice zurück, die durch Zeichen getrennt sind, die durch ein Muster übereinstimmen.
11 Zeichen () pub fn chars (& self) → Zeichen Gibt einen Iterator über die Zeichen eines String-Slice zurück.

Abbildung: neu ()

Ein leeres String-Objekt wird mit dem erstellt new()Methode und ihr Wert wird auf Hallo gesetzt .

fn main(){
   let mut z = String::new();
   z.push_str("hello");
   println!("{}",z);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

hello

Abbildung: to_string ()

Um auf alle Methoden des String-Objekts zuzugreifen, konvertieren Sie ein String-Literal mit dem in den Objekttyp to_string() Funktion.

fn main(){
   let name1 = "Hello TutorialsPoint , 
   Hello!".to_string();
   println!("{}",name1);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

Hello TutorialsPoint , Hello!

Abbildung: ersetzen ()

Das replace()Die Funktion benötigt zwei Parameter: Der erste Parameter ist ein zu suchendes Zeichenfolgenmuster und der zweite Parameter ist der neue Wert, der ersetzt werden soll. Im obigen Beispiel wird Hello zweimal in der Zeichenfolge name1 angezeigt .

Die Ersetzungsfunktion ersetzt alle Vorkommen der Zeichenfolge Hello mit Howdy.

fn main(){
   let name1 = "Hello TutorialsPoint , 
   Hello!".to_string();         //String object
   let name2 = name1.replace("Hello","Howdy");    //find and replace
   println!("{}",name2);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

Howdy TutorialsPoint , Howdy!

Abbildung: as_str ()

Das as_str() Die Funktion extrahiert ein String-Slice, das den gesamten String enthält.

fn main() {
   let example_string = String::from("example_string");
   print_literal(example_string.as_str());
}
fn print_literal(data:&str ){
   println!("displaying string literal {}",data);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

displaying string literal example_string

Abbildung: push ()

Das push() Die Funktion hängt das angegebene Zeichen an das Ende dieses Strings an.

fn main(){
   let mut company = "Tutorial".to_string();
   company.push('s');
   println!("{}",company);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

Tutorials

Abbildung: push_str ()

Das push_str() Die Funktion hängt ein bestimmtes String-Slice an das Ende eines Strings an.

fn main(){
   let mut company = "Tutorials".to_string();
   company.push_str(" Point");
   println!("{}",company);
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

Tutorials Point

Abbildung: len ()

Das len() Die Funktion gibt die Gesamtzahl der Zeichen in einer Zeichenfolge (einschließlich Leerzeichen) zurück.

fn main() {
   let fullname = " Tutorials Point";
   println!("length is {}",fullname.len());
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

length is 20

Abbildung: trim ()

Die Funktion trim () entfernt führende und nachfolgende Leerzeichen in einer Zeichenfolge. HINWEIS: Mit dieser Funktion werden die Inline-Leerzeichen nicht entfernt.

fn main() {
   let fullname = " Tutorials Point \r\n";
   println!("Before trim ");
   println!("length is {}",fullname.len());
   println!();
   println!("After trim ");
   println!("length is {}",fullname.trim().len());
}

Ausgabe

Das obige Programm erzeugt die folgende Ausgabe -

Before trim
length is 24

After trim
length is 15

Abbildung: split_whitespace ()

Das split_whitespace()teilt die Eingabezeichenfolge in verschiedene Zeichenfolgen auf. Es gibt einen Iterator zurück, sodass wir die Token wie unten gezeigt durchlaufen.

fn main(){
   let msg = "Tutorials Point has good t
   utorials".to_string();
   let mut i = 1;
   
   for token in msg.split_whitespace(){
      println!("token {} {}",i,token);
      i+=1;
   }
}

Ausgabe

token 1 Tutorials
token 2 Point
token 3 has
token 4 good
token 5 tutorials

Abbildung: split () Zeichenfolge

Das split() stringDie Methode gibt einen Iterator über Teilzeichenfolgen eines String-Slice zurück, die durch Zeichen getrennt sind, die durch ein Muster übereinstimmen. Die Einschränkung der split () -Methode besteht darin, dass das Ergebnis nicht zur späteren Verwendung gespeichert werden kann. Dascollect Methode kann verwendet werden, um das von split () zurückgegebene Ergebnis als Vektor zu speichern.

fn main() {
   let fullname = "Kannan,Sudhakaran,Tutorialspoint";

   for token in fullname.split(","){
      println!("token is {}",token);
   }

   //store in a Vector
   println!("\n");
   let tokens:Vec<&str>= fullname.split(",").collect();
   println!("firstName is {}",tokens[0]);
   println!("lastname is {}",tokens[1]);
   println!("company is {}",tokens[2]);
}

Das obige Beispiel teilt die Zeichenfolge fullname, wann immer es auf ein Komma stößt (,).

Ausgabe

token is Kannan
token is Sudhakaran
token is Tutorialspoint

firstName is Kannan
lastname is Sudhakaran
company is Tutorialspoint

Abbildung: Zeichen ()

Auf einzelne Zeichen in einer Zeichenfolge kann mit der Zeichenmethode zugegriffen werden. Betrachten wir ein Beispiel, um dies zu verstehen.

fn main(){
   let n1 = "Tutorials".to_string();

   for n in n1.chars(){
      println!("{}",n);
   }
}

Ausgabe

T
u
t
o
r
i
a
l
s

Verkettung von Strings mit dem Operator +

Ein Zeichenfolgenwert kann an eine andere Zeichenfolge angehängt werden. Dies wird als Verkettung oder Interpolation bezeichnet. Das Ergebnis der Zeichenfolgenverkettung ist ein neues Zeichenfolgenobjekt. Der Operator + verwendet intern eine Add- Methode. Die Syntax der Add-Funktion akzeptiert zwei Parameter. Der erste Parameter ist self - das String-Objekt selbst und der zweite Parameter ist eine Referenz des zweiten String-Objekts. Dies ist unten gezeigt -

//add function
add(self,&str)->String { 
   // returns a String object
}

Abbildung: String-Verkettung

fn main(){
   let n1 = "Tutorials".to_string();
   let n2 = "Point".to_string();

   let n3 = n1 + &n2; // n2 reference is passed
   println!("{}",n3);
}

Die Ausgabe erfolgt wie unten angegeben

TutorialsPoint

Abbildung: Typ Casting

Das folgende Beispiel zeigt die Konvertierung einer Zahl in ein Zeichenfolgenobjekt.

fn main(){
   let number = 2020;
   let number_as_string = number.to_string(); 
   
   // convert number to string
   println!("{}",number_as_string);
   println!("{}",number_as_string=="2020");
}

Die Ausgabe erfolgt wie unten angegeben

2020
true

Abbildung: Format! Makro

Eine andere Möglichkeit, String-Objekte zusammen hinzuzufügen, ist die Verwendung einer Makrofunktion namens Format. Die Verwendung von Format! ist wie unten gezeigt.

fn main(){
   let n1 = "Tutorials".to_string();
   let n2 = "Point".to_string();
   let n3 = format!("{} {}",n1,n2);
   println!("{}",n3);
}

Die Ausgabe erfolgt wie unten angegeben

Tutorials Point

Ein Operator definiert eine Funktion, die für die Daten ausgeführt wird. Die Daten, mit denen Operatoren arbeiten, werden als Operanden bezeichnet. Betrachten Sie den folgenden Ausdruck -

7 + 5 = 12

Hier sind die Werte 7, 5 und 12 Operanden, während + und = Operatoren sind.

Die Hauptbetreiber in Rust können klassifiziert werden als -

  • Arithmetic
  • Bitwise
  • Comparison
  • Logical
  • Bitwise
  • Conditional

Rechenzeichen

Angenommen, die Werte in den Variablen a und b sind 10 bzw. 5.

Beispiele anzeigen

Sr.Nr. Operator Beschreibung Beispiel
1 + (Ergänzung) Gibt die Summe der Operanden zurück a + b ist 15
2 -(Subtraktion) Gibt die Differenz der Werte zurück ab ist 5
3 * (Multiplikation) gibt das Produkt der Werte zurück a * b ist 50
4 / (Teilung) führt eine Divisionsoperation durch und gibt den Quotienten zurück a / b ist 2
5 % (Modul) führt eine Divisionsoperation durch und gibt den Rest zurück a% b ist 0

NOTE - Die Operatoren ++ und - werden in Rust nicht unterstützt.

Vergleichsoperatoren

Relationale Operatoren testen oder definieren die Art der Beziehung zwischen zwei Entitäten. Vergleichsoperatoren werden verwendet, um zwei oder mehr Werte zu vergleichen. Vergleichsoperatoren geben einen booleschen Wert zurück - true oder false.

Angenommen, der Wert von A ist 10 und B ist 20.

Beispiele anzeigen

Sr.Nr. Operator Beschreibung Beispiel
1 > Größer als (A> B) ist falsch
2 < Weniger als (A <B) ist wahr
3 > = Größer als oder gleich wie (A> = B) ist falsch
4 <= Kleiner als oder gleich (A <= B) ist wahr
5 == Gleichberechtigung (A == B) ist falsch
6 ! = Nicht gleich (A! = B) ist wahr

Logische Operatoren

Logische Operatoren werden verwendet, um zwei oder mehr Bedingungen zu kombinieren. Auch logische Operatoren geben einen booleschen Wert zurück. Angenommen, der Wert der Variablen A ist 10 und B ist 20.

Beispiele anzeigen

Sr.Nr. Operator Beschreibung Beispiel
1 && (Und) Der Operator gibt nur dann true zurück, wenn alle angegebenen Ausdrücke true zurückgeben (A> 10 && B> 10) ist falsch
2 || (ODER) Der Operator gibt true zurück, wenn mindestens einer der angegebenen Ausdrücke true zurückgibt (A> 10 || B> 10) ist wahr
3 ! (NICHT) Der Operator gibt die Umkehrung des Ergebnisses des Ausdrucks zurück. Zum Beispiel :! (> 5) gibt false zurück ! (A> 10) ist wahr

Bitweise Operatoren

Angenommen, Variable A = 2 und B = 3.

Beispiele anzeigen

Sr.Nr. Operator Beschreibung Beispiel
1 & (Bitweises UND) Es führt eine boolesche UND-Operation für jedes Bit seiner ganzzahligen Argumente aus. (A & B) ist 2
2 | (BitWise ODER) Es führt eine boolesche ODER-Operation für jedes Bit seiner ganzzahligen Argumente aus. (A | B) ist 3
3 ^ (Bitweises XOR) Es führt eine boolesche exklusive ODER-Operation für jedes Bit seiner ganzzahligen Argumente aus. Exklusives ODER bedeutet, dass entweder Operand eins wahr ist oder Operand zwei wahr ist, aber nicht beide. (A ^ B) ist 1
4 ! (Bitweise nicht) Es ist ein unärer Operator und arbeitet durch Umkehren aller Bits im Operanden. (! B) ist -4
5 << (Linksverschiebung) Es verschiebt alle Bits in seinem ersten Operanden um die im zweiten Operanden angegebene Anzahl von Stellen nach links. Neue Bits werden mit Nullen gefüllt. Das Verschieben eines Werts um eine Position entspricht dem Multiplizieren mit 2, das Verschieben von zwei Positionen entspricht dem Multiplizieren mit 4 usw. (A << 1) ist 4
6 >> (Rechtsverschiebung) Binärer Rechtsschieber. Der Wert des linken Operanden wird um die vom rechten Operanden angegebene Anzahl von Bits nach rechts verschoben. (A >> 1) ist 1
7 >>> (Rechtsverschiebung mit Null) Dieser Operator ist genau wie der >> -Operator, außer dass die nach links verschobenen Bits immer Null sind. (A >>> 1) ist 1

Entscheidungsstrukturen erfordern, dass der Programmierer eine oder mehrere Bedingungen angibt, die vom Programm bewertet oder getestet werden sollen, zusammen mit einer Anweisung oder Anweisungen, die ausgeführt werden sollen, wenn die Bedingung als wahr bestimmt wird, und optional anderen Anweisungen, die ausgeführt werden sollen, wenn die Bedingung wird als falsch bestimmt.

Im Folgenden ist die allgemeine Form einer typischen Entscheidungsstruktur dargestellt, die in den meisten Programmiersprachen zu finden ist.

Sr.Nr. Aussage & Beschreibung
1

if statement

Eine if- Anweisung besteht aus einem booleschen Ausdruck, gefolgt von einer oder mehreren Anweisungen.

2

if...else statement

Auf eine if- Anweisung kann eine optionale else- Anweisung folgen , die ausgeführt wird, wenn der Boolesche Ausdruck false ist.

3

else...if and nested ifstatement

Sie können eine if- oder if- Anweisung in einer anderen if- oder if- Anweisung verwenden.

4

match statement

Mit einer Übereinstimmungsanweisung kann eine Variable anhand einer Werteliste getestet werden.

If-Anweisung

Das Konstrukt if… else wertet eine Bedingung aus, bevor ein Codeblock ausgeführt wird.

Syntax

if boolean_expression {
   // statement(s) will execute if the boolean expression is true
}

Wenn der Boolesche Ausdruck true ergibt, wird der Codeblock in der if-Anweisung ausgeführt. Wenn der Boolesche Ausdruck false ergibt, wird der erste Codesatz nach dem Ende der if-Anweisung (nach der schließenden geschweiften Klammer) ausgeführt.

fn main(){
   let num:i32 = 5;
   if num > 0 {
      println!("number is positive") ;
   }
}

Das obige Beispiel wird gedruckt number is positive als die vom if-Block angegebene Bedingung ist wahr.

if else-Anweisung

Ein if kann von einem optionalen gefolgt werden elseBlock. Der else-Block wird ausgeführt, wenn der von der if-Anweisung getestete Boolesche Ausdruck false ergibt.

Syntax

if boolean_expression {
   // statement(s) will execute if the boolean expression is true
} else {
   // statement(s) will execute if the boolean expression is false
}

FlowChart

Das ifBlock schützt den bedingten Ausdruck. Der der if-Anweisung zugeordnete Block wird ausgeführt, wenn der boolesche Ausdruck true ergibt.

Dem if-Block kann eine optionale else-Anweisung folgen. Der dem else-Block zugeordnete Anweisungsblock wird ausgeführt, wenn der Ausdruck false ergibt.

Illustration - Einfach wenn ... sonst

fn main() {
   let num = 12;
   if num % 2==0 {
      println!("Even");
   } else {
      println!("Odd");
   }
}

Das obige Beispiel gibt an, ob der Wert in einer Variablen gerade oder ungerade ist. Der if-Block überprüft die Teilbarkeit des Werts durch 2, um denselben zu bestimmen. Hier ist die Ausgabe des obigen Codes -

Even

Verschachteltes Wenn

Das else…ifLeiter ist nützlich, um mehrere Bedingungen zu testen. Die Syntax ist wie folgt:

Syntax

if boolean_expression1 {
   //statements if the expression1 evaluates to true
} else if boolean_expression2 {
   //statements if the expression2 evaluates to true
} else {
   //statements if both expression1 and expression2 result to false
}

Bei der Verwendung von if… else… if- und else-Anweisungen sind einige Punkte zu beachten.

  • Ein if kann null oder eins haben und es muss nach jedem anderen kommen..if.
  • Ein Wenn kann null bis viele andere haben ... wenn und sie müssen vor dem Anderen kommen.
  • Sobald ein else..if erfolgreich ist, wird keines der verbleibenden else..if oder else getestet.

Beispiel: sonst… wenn Leiter

fn main() {
   let num = 2 ;
   if num > 0 {
      println!("{} is positive",num);
   } else if num < 0 {
      println!("{} is negative",num);
   } else {
      println!("{} is neither positive nor negative",num) ;
   }
}

Das Snippet zeigt an, ob der Wert positiv, negativ oder null ist.

Ausgabe

2 is positive

Übereinstimmungserklärung

Die match-Anweisung prüft, ob ein aktueller Wert aus einer Werteliste übereinstimmt. Dies ist der switch-Anweisung in der Sprache C sehr ähnlich. Beachten Sie zunächst, dass der Ausdruck nach dem Schlüsselwort match nicht in Klammern stehen muss.

Die Syntax ist wie folgt.

let expressionResult = match variable_expression {
   constant_expr1 => {
      //statements;
   },
   constant_expr2 => {
      //statements;
   },
   _ => {
      //default
   }
};

In dem unten angegebenen Beispiel state_code wird mit einer Liste von Werten abgeglichen MH, KL, KA, GA- wenn eine Übereinstimmung gefunden wird, wird ein String - Wert der Variablen zurück Zustand . Wenn keine Übereinstimmung gefunden wird, wird der Standardfall _ übereinstimmt und der Wert Unbekannt zurückgegeben.

fn main(){
   let state_code = "MH";
   let state = match state_code {
      "MH" => {println!("Found match for MH"); "Maharashtra"},
      "KL" => "Kerala",
      "KA" => "Karnadaka",
      "GA" => "Goa",
      _ => "Unknown"
   };
   println!("State name is {}",state);
}

Ausgabe

Found match for MH
State name is Maharashtra

Es kann Fälle geben, in denen ein Codeblock wiederholt ausgeführt werden muss. Im Allgemeinen werden Programmieranweisungen nacheinander ausgeführt: Die erste Anweisung in einer Funktion wird zuerst ausgeführt, gefolgt von der zweiten usw.

Programmiersprachen bieten verschiedene Steuerungsstrukturen, die kompliziertere Ausführungspfade ermöglichen.

Mit einer Schleifenanweisung können wir eine Anweisung oder eine Gruppe von Anweisungen mehrmals ausführen. Im Folgenden ist die allgemeine Form einer Schleifenanweisung in den meisten Programmiersprachen angegeben.

Rust bietet verschiedene Arten von Schleifen, um die Schleifenanforderungen zu erfüllen -

  • while
  • loop
  • for

Definitive Schleife

Eine Schleife, deren Anzahl von Iterationen definitiv / fest ist, wird als bestimmte Schleife bezeichnet. Dasfor Schleife ist eine Implementierung einer bestimmten Schleife.

Für Schleife

Die for-Schleife führt den Codeblock für eine bestimmte Anzahl von Malen aus. Es kann verwendet werden, um einen festen Satz von Werten zu durchlaufen, z. B. ein Array. Die Syntax der for-Schleife ist wie folgt

Syntax

for temp_variable in lower_bound..upper_bound {
   //statements
}

Ein Beispiel für eine for-Schleife ist unten dargestellt

fn main(){
   for x in 1..11{ // 11 is not inclusive
      if x==5 {
         continue;
      }
      println!("x is {}",x);
   }
}

NOTE: dass auf die Variable x nur innerhalb des for-Blocks zugegriffen werden kann.

Ausgabe

x is 1
x is 2
x is 3
x is 4
x is 6
x is 7
x is 8
x is 9
x is 10

Unbestimmte Schleife

Eine unbestimmte Schleife wird verwendet, wenn die Anzahl der Iterationen in einer Schleife unbestimmt oder unbekannt ist.

Unbestimmte Schleifen können implementiert werden mit -

Sr.Nr. Name & Beschreibung
1

While

Die while- Schleife führt die Anweisungen jedes Mal aus, wenn die angegebene Bedingung als wahr ausgewertet wird

2

Loop

Die Schleife ist eine while (true) unbestimmte Schleife

Illustration - für eine Weile

fn main(){
   let mut x = 0;
   while x < 10{
      x+=1;
      println!("inside loop x value is {}",x);
   }
   println!("outside loop x value is {}",x);
}

Die Ausgabe ist wie unten gezeigt -

inside loop x value is 1
inside loop x value is 2
inside loop x value is 3
inside loop x value is 4
inside loop x value is 5
inside loop x value is 6
inside loop x value is 7
inside loop x value is 8
inside loop x value is 9
inside loop x value is 10
outside loop x value is 10

Illustrationsschleife

fn main(){
   //while true

   let mut x = 0;
   loop {
      x+=1;
      println!("x={}",x);

      if x==15 {
         break;
      }
   }
}

Das breakAnweisung wird verwendet, um die Kontrolle aus einem Konstrukt zu entfernen. Wenn Sie break in a loop verwenden, verlässt das Programm die Schleife.

Ausgabe

x=1
x=2
x=3
x=4
x=5
x=6
x=7
x=8
x=9
x=10
x=11
x=12
x=13
x=14
x=15

Erklärung fortsetzen

Die continue-Anweisung überspringt die nachfolgenden Anweisungen in der aktuellen Iteration und bringt die Steuerung zurück zum Anfang der Schleife. Im Gegensatz zur break-Anweisung verlässt die continue die Schleife nicht. Es beendet die aktuelle Iteration und startet die nachfolgende Iteration.

Ein Beispiel für die continue-Anweisung ist unten angegeben.

fn main() {

   let mut count = 0;

   for num in 0..21 {
      if num % 2==0 {
         continue;
      }
      count+=1;
   }
   println! (" The count of odd values between 0 and 20 is: {} ",count);
   //outputs 10
}

Das obige Beispiel zeigt die Anzahl der geraden Werte zwischen 0 und 20. Die Schleife verlässt die aktuelle Iteration, wenn die Zahl gerade ist. Dies wird mit der continue-Anweisung erreicht.

Die Anzahl der ungeraden Werte zwischen 0 und 20 beträgt 10

Funktionen sind die Bausteine ​​für lesbaren, wartbaren und wiederverwendbaren Code. Eine Funktion ist eine Reihe von Anweisungen zum Ausführen einer bestimmten Aufgabe. Funktionen organisieren das Programm in logischen Codeblöcken. Einmal definiert, können Funktionen aufgerufen werden, um auf Code zuzugreifen. Dies macht den Code wiederverwendbar. Darüber hinaus erleichtern Funktionen das Lesen und Verwalten des Programmcodes.

Eine Funktionsdeklaration informiert den Compiler über den Namen, den Rückgabetyp und die Parameter einer Funktion. Eine Funktionsdefinition liefert den tatsächlichen Hauptteil der Funktion.

Sr.Nr. Bedienungsanleitung
1

Defining a function

Die TA-Funktionsdefinition gibt an, was und wie eine bestimmte Aufgabe ausgeführt werden soll.

2

Calling or invoking a Function

Eine Funktion muss aufgerufen werden, um sie auszuführen.

3

Returning Functions

Funktionen können zusammen mit der Steuerung auch einen Wert an den Anrufer zurückgeben.

4

Parameterized Function

Parameter sind ein Mechanismus zum Übergeben von Werten an Funktionen.

Funktion definieren

Eine Funktionsdefinition gibt an, was und wie eine bestimmte Aufgabe ausgeführt werden soll. Vor der Verwendung einer Funktion muss diese definiert werden. Der Funktionskörper enthält Code, der von der Funktion ausgeführt werden soll. Die Regeln für die Benennung einer Funktion ähneln denen einer Variablen. Funktionen werden mit dem definiertfnStichwort. Die Syntax zum Definieren einer Standardfunktion ist unten angegeben

Syntax

fn function_name(param1,param2..paramN) {
   // function body
}

Eine Funktionsdeklaration kann optional Parameter / Argumente enthalten. Parameter werden verwendet, um Werte an Funktionen zu übergeben.

Beispiel - Einfache Funktionsdefinition

//Defining a function
fn fn_hello(){
   println!("hello from function fn_hello ");
}

Aufrufen einer Funktion

Eine Funktion muss aufgerufen werden, um sie auszuführen. Dieser Vorgang wird als bezeichnetfunction invocation. Werte für Parameter sollten übergeben werden, wenn eine Funktion aufgerufen wird. Die Funktion, die eine andere Funktion aufruft, heißtcaller function.

Syntax

function_name(val1,val2,valN)

Beispiel: Aufrufen einer Funktion

fn main(){
   //calling a function
   fn_hello();
}

Hier ist main () die Aufruferfunktion.

Illustration

Das folgende Beispiel definiert eine Funktion fn_hello(). Die Funktion druckt eine Nachricht an die Konsole. Dasmain()Funktion ruft die Funktion fn_hello () auf .

fn main(){
   //calling a function
   fn_hello();
}
//Defining a function
fn fn_hello(){
   println!("hello from function fn_hello ");
}

Ausgabe

hello from function fn_hello

Wert von einer Funktion zurückgeben

Funktionen können auch einen Wert zusammen mit der Steuerung an den Anrufer zurückgeben. Solche Funktionen werden als Rückgabefunktionen bezeichnet.

Syntax

Jede der folgenden Syntax kann verwendet werden, um eine Funktion mit dem Rückgabetyp zu definieren.

Mit return-Anweisung

// Syntax1
fn function_name() -> return_type {
   //statements
   return value;
}

Kurzsyntax ohne return-Anweisung

//Syntax2
fn function_name() -> return_type {
   value //no semicolon means this value is returned
}

Illustration

fn main(){
   println!("pi value is {}",get_pi());
}
fn get_pi()->f64 {
   22.0/7.0
}

Ausgabe

pi value is 3.142857142857143

Funktion mit Parametern

Parameter sind ein Mechanismus zum Übergeben von Werten an Funktionen. Parameter bilden einen Teil der Funktionssignatur. Die Parameterwerte werden beim Aufruf an die Funktion übergeben. Sofern nicht ausdrücklich angegeben, muss die Anzahl der an eine Funktion übergebenen Werte mit der Anzahl der definierten Parameter übereinstimmen.

Parameter können mit einer der folgenden Techniken an eine Funktion übergeben werden:

Wert übergeben

Wenn eine Methode aufgerufen wird, wird für jeden Wertparameter ein neuer Speicherort erstellt. Die Werte der Istparameter werden in diese kopiert. Daher haben die am Parameter in der aufgerufenen Methode vorgenommenen Änderungen keine Auswirkung auf das Argument.

Im folgenden Beispiel wird eine Variable no deklariert, die anfänglich 5 ist. Die Variable wird als Parameter (nach Wert) an die übergeben mutate_no_to_zero()Funktion, die den Wert auf Null ändert. Nach dem Funktionsaufruf, wenn die Steuerung zur Hauptmethode zurückkehrt, ist der Wert derselbe.

fn main(){
   let no:i32 = 5;
   mutate_no_to_zero(no);
   println!("The value of no is:{}",no);
}

fn mutate_no_to_zero(mut param_no: i32) {
   param_no = param_no*0;
   println!("param_no value is :{}",param_no);
}

Ausgabe

param_no value is :0
The value of no is:5

Referenz übergeben

Wenn Sie Parameter als Referenz übergeben, wird im Gegensatz zu Wertparametern kein neuer Speicherort für diese Parameter erstellt. Die Referenzparameter stellen denselben Speicherort dar wie die tatsächlichen Parameter, die der Methode zur Verfügung gestellt werden. Parameterwerte können als Referenz übergeben werden, indem dem Variablennamen ein vorangestellt wird& .

Im folgenden Beispiel haben wir eine Variable no , die anfänglich 5 ist. Ein Verweis auf die Variable no wird an die übergebenmutate_no_to_zero()Funktion. Die Funktion arbeitet mit der ursprünglichen Variablen. Wenn die Steuerung nach dem Funktionsaufruf zur Hauptmethode zurückkehrt, ist der Wert der ursprünglichen Variablen Null.

fn main() {
   let mut no:i32 = 5;
   mutate_no_to_zero(&mut no);
   println!("The value of no is:{}",no);
}
fn mutate_no_to_zero(param_no:&mut i32){
   *param_no = 0; //de reference
}

Der Operator * wird verwendet, um auf den Wert zuzugreifen, der an dem Speicherort der Variablen gespeichert ist param_noverweist auf. Dies wird auch als Dereferenzierung bezeichnet.

Die Ausgabe wird -

The value of no is 0.

String an eine Funktion übergeben

Die Funktion main () übergibt ein Zeichenfolgenobjekt an die Funktion display () .

fn main(){
   let name:String = String::from("TutorialsPoint");
   display(name); 
   //cannot access name after display
}
fn display(param_name:String){
   println!("param_name value is :{}",param_name);
}

Ausgabe

param_name value is :TutorialsPoint

Tupel ist ein zusammengesetzter Datentyp. Ein Skalartyp kann nur einen Datentyp speichern. Beispielsweise kann eine i32-Variable nur einen einzigen ganzzahligen Wert speichern. In zusammengesetzten Typen können mehrere Werte gleichzeitig gespeichert werden, und es kann sich um verschiedene Typen handeln.

Tupel haben eine feste Länge - einmal deklariert, können sie nicht wachsen oder schrumpfen. Der Tupelindex beginnt bei0.

Syntax

//Syntax1
let tuple_name:(data_type1,data_type2,data_type3) = (value1,value2,value3);

//Syntax2
let tuple_name = (value1,value2,value3);

Illustration

Das folgende Beispiel zeigt die Werte in einem Tupel an.

fn main() {
   let tuple:(i32,f64,u8) = (-325,4.9,22);
   println!("{:?}",tuple);
}

Die Syntax println! ("{}", Tupel) kann nicht zum Anzeigen von Werten in einem Tupel verwendet werden. Dies liegt daran, dass ein Tupel ein zusammengesetzter Typ ist. Verwenden Sie die Syntax println! ("{:?}", Tuple_name) , um Werte in einem Tupel zu drucken.

Ausgabe

(-325, 4.9, 22)

Illustration

Im folgenden Beispiel werden einzelne Werte in einem Tupel gedruckt.

fn main() {
   let tuple:(i32,f64,u8) = (-325,4.9,22);
   println!("integer is :{:?}",tuple.0);
   println!("float is :{:?}",tuple.1);
   println!("unsigned integer is :{:?}",tuple.2);
}

Ausgabe

integer is :-325
float is :4.9
unsigned integer is :2

Illustration

Im folgenden Beispiel wird ein Tupel als Parameter an eine Funktion übergeben. Tupel werden als Wert an Funktionen übergeben.

fn main(){
   let b:(i32,bool,f64) = (110,true,10.9);
   print(b);
}
//pass the tuple as a parameter

fn print(x:(i32,bool,f64)){
   println!("Inside print method");
   println!("{:?}",x);
}

Ausgabe

Inside print method
(110, true, 10.9)

Zerstören

Die Zerstörung der Zuordnung ist ein Merkmal von Rost, bei dem wir die Werte eines Tupels auspacken. Dies wird erreicht, indem verschiedenen Variablen ein Tupel zugewiesen wird.

Betrachten Sie das folgende Beispiel -

fn main(){
   let b:(i32,bool,f64) = (30,true,7.9);
   print(b);
}
fn print(x:(i32,bool,f64)){
   println!("Inside print method");
   let (age,is_male,cgpa) = x; //assigns a tuple to 
   distinct variables
   println!("Age is {} , isMale? {},cgpa is 
   {}",age,is_male,cgpa);
}

Die Variable x ist ein Tupel, das der let-Anweisung zugewiesen ist. Jede Variable - age, is_male und cgpa enthält die entsprechenden Werte in einem Tupel.

Ausgabe

Inside print method
Age is 30 , isMale? true,cgpa is 7.9

In diesem Kapitel lernen wir ein Array und die verschiedenen damit verbundenen Funktionen kennen. Bevor wir uns mit Arrays befassen, wollen wir sehen, wie sich ein Array von einer Variablen unterscheidet.

Variablen haben die folgenden Einschränkungen:

  • Variablen sind skalarer Natur. Mit anderen Worten, eine Variablendeklaration kann jeweils nur einen Wert enthalten. Dies bedeutet, dass zum Speichern von n Werten in einem Programm n eine Variablendeklaration erforderlich ist. Daher ist die Verwendung von Variablen nicht möglich, wenn eine größere Sammlung von Werten gespeichert werden muss.

  • Variablen in einem Programm wird Speicher in zufälliger Reihenfolge zugewiesen, wodurch es schwierig wird, die Werte in der Reihenfolge ihrer Deklaration abzurufen / zu lesen.

Ein Array ist eine homogene Sammlung von Werten. Einfach ausgedrückt ist ein Array eine Sammlung von Werten desselben Datentyps.

Merkmale eines Arrays

Die Funktionen eines Arrays sind wie folgt aufgeführt:

  • Eine Array-Deklaration weist sequentielle Speicherblöcke zu.

  • Arrays sind statisch. Dies bedeutet, dass die Größe eines einmal initialisierten Arrays nicht geändert werden kann.

  • Jeder Speicherblock repräsentiert ein Array-Element.

  • Array-Elemente werden durch eine eindeutige Ganzzahl identifiziert, die als Index / Index des Elements bezeichnet wird.

  • Das Auffüllen der Array-Elemente wird als Array-Initialisierung bezeichnet.

  • Array-Elementwerte können aktualisiert oder geändert, aber nicht gelöscht werden.

Arrays deklarieren und initialisieren

Verwenden Sie die unten angegebene Syntax, um ein Array in Rust zu deklarieren und zu initialisieren.

Syntax

//Syntax1
let variable_name = [value1,value2,value3];

//Syntax2
let variable_name:[dataType;size] = [value1,value2,value3];

//Syntax3
let variable_name:[dataType;size] = [default_value_for_elements,size];

In der ersten Syntax wird der Typ des Arrays während der Initialisierung aus dem Datentyp des ersten Elements des Arrays abgeleitet.

Abbildung: Einfaches Array

Das folgende Beispiel gibt explizit die Größe und den Datentyp des Arrays an. Die {:?} Syntax der Funktion println! () Wird verwendet, um alle Werte im Array zu drucken. Die Funktion len () wird verwendet, um die Größe des Arrays zu berechnen.

fn main(){
   let arr:[i32;4] = [10,20,30,40];
   println!("array is {:?}",arr);
   println!("array size is :{}",arr.len());
}

Ausgabe

array is [10, 20, 30, 40]
array size is :4

Abbildung: Array ohne Datentyp

Das folgende Programm deklariert ein Array von 4 Elementen. Der Datentyp wird in der Variablendeklaration nicht explizit angegeben. In diesem Fall ist das Array vom Typ Integer. Die Funktion len () wird verwendet, um die Größe des Arrays zu berechnen.

fn main(){
   let arr = [10,20,30,40];
   println!("array is {:?}",arr);
   println!("array size is :{}",arr.len());
}

Ausgabe

array is [10, 20, 30, 40]
array size is :4

Abbildung: Standardwerte

Im folgenden Beispiel wird ein Array erstellt und alle seine Elemente mit dem Standardwert -1 initialisiert .

fn main() {
   let arr:[i32;4] = [-1;4];
   println!("array is {:?}",arr);
   println!("array size is :{}",arr.len());
}

Ausgabe

array is [-1, -1, -1, -1]
array size is :4

Abbildung: Array mit for-Schleife

Das folgende Beispiel durchläuft ein Array und druckt die Indizes und ihre entsprechenden Werte. Die Schleife ruft Werte vom Index 0 bis 4 (Index des letzten Array-Elements) ab.

fn main(){
   let arr:[i32;4] = [10,20,30,40];
   println!("array is {:?}",arr);
   println!("array size is :{}",arr.len());

   for index in 0..4 {
      println!("index is: {} & value is : {}",index,arr[index]);
   }
}

Ausgabe

array is [10, 20, 30, 40]
array size is :4
index is: 0 & value is : 10
index is: 1 & value is : 20
index is: 2 & value is : 30
index is: 3 & value is : 40

Abbildung: Verwenden der Funktion iter ()

Die Funktion iter () ruft Werte aller Elemente in einem Array ab.

fn main(){

let arr:[i32;4] = [10,20,30,40];
   println!("array is {:?}",arr);
   println!("array size is :{}",arr.len());

   for val in arr.iter(){
      println!("value is :{}",val);
   }
}

Ausgabe

array is [10, 20, 30, 40]
array size is :4
value is :10
value is :20
value is :30
value is :40

Abbildung: Veränderbares Array

Mit dem Schlüsselwort mut kann ein veränderbares Array deklariert werden. Das folgende Beispiel deklariert ein veränderbares Array und ändert den Wert des zweiten Array-Elements.

fn main(){
   let mut arr:[i32;4] = [10,20,30,40];
   arr[1] = 0;
   println!("{:?}",arr);
}

Ausgabe

[10, 0, 30, 40]

Übergeben von Arrays als Parameter an Funktionen

Ein Array kann nach Wert oder unter Bezugnahme auf Funktionen übergeben werden.

Abbildung: Wert übergeben

fn main() {
   let arr = [10,20,30];
   update(arr);

   print!("Inside main {:?}",arr);
}
fn update(mut arr:[i32;3]){
   for i in 0..3 {
      arr[i] = 0;
   }
   println!("Inside update {:?}",arr);
}

Ausgabe

Inside update [0, 0, 0]
Inside main [10, 20, 30]

Abbildung: Als Referenz übergeben

fn main() {
   let mut arr = [10,20,30];
   update(&mut arr);
   print!("Inside main {:?}",arr);
}
fn update(arr:&mut [i32;3]){
   for i in 0..3 {
      arr[i] = 0;
   }
   println!("Inside update {:?}",arr);
}

Ausgabe

Inside update [0, 0, 0]
Inside main [0, 0, 0]

Array-Deklaration und Konstanten

Betrachten wir ein Beispiel unten, um die Array-Deklaration und -Konstanten zu verstehen.

fn main() {
   let N: usize = 20;
   let arr = [0; N]; //Error: non-constant used with constant
   print!("{}",arr[10])
}

Der Compiler führt zu einer Ausnahme. Dies liegt daran, dass die Länge eines Arrays zur Kompilierungszeit bekannt sein muss. Hier wird zur Laufzeit der Wert der Variablen "N" ermittelt. Mit anderen Worten, Variablen können nicht zum Definieren der Größe eines Arrays verwendet werden.

Das folgende Programm ist jedoch gültig -

fn main() {
   const N: usize = 20; 
   // pointer sized
   let arr = [0; N];

   print!("{}",arr[10])
}

Der Wert eines Bezeichners mit dem Schlüsselwort const wird zur Kompilierungszeit definiert und kann zur Laufzeit nicht geändert werden. usize hat eine Zeigergröße, daher hängt seine tatsächliche Größe von der Architektur ab, für die Sie Ihr Programm kompilieren.

Der Speicher für ein Programm kann wie folgt zugewiesen werden:

  • Stack
  • Heap

Stapel

Ein Stapel folgt einem letzten in der ersten Reihenfolge. Der Stapel speichert Datenwerte, deren Größe zur Kompilierungszeit bekannt ist. Beispielsweise ist eine Variable mit fester Größe i32 ein Kandidat für die Stapelzuweisung. Seine Größe ist zur Kompilierungszeit bekannt. Alle Skalartypen können im Stapel gespeichert werden, wenn die Größe festgelegt ist.

Betrachten Sie ein Beispiel für eine Zeichenfolge, der zur Laufzeit ein Wert zugewiesen wird. Die genaue Größe einer solchen Zeichenfolge kann zur Kompilierungszeit nicht ermittelt werden. Es ist also kein Kandidat für die Stapelzuweisung, sondern für die Heapzuweisung.

Haufen

Der Heapspeicher speichert Datenwerte, deren Größe zur Kompilierungszeit unbekannt ist. Es wird zum Speichern dynamischer Daten verwendet. Einfach ausgedrückt wird ein Heapspeicher Datenwerten zugewiesen, die sich während des gesamten Lebenszyklus des Programms ändern können. Der Heap ist ein Bereich im Speicher, der im Vergleich zum Stapel weniger organisiert ist.

Was ist Eigentum?

Jeder Wert in Rust hat eine Variable, die aufgerufen wird ownerdes Wertes. Allen in Rust gespeicherten Daten ist ein Eigentümer zugeordnet. In der Syntax - let age = 30 - ist age beispielsweise der Eigentümer des Werts 30 .

  • Jede Daten kann jeweils nur einen Eigentümer haben.

  • Zwei Variablen können nicht auf denselben Speicherort verweisen. Die Variablen zeigen immer auf verschiedene Speicherorte.

Eigentumsübertragung

Das Eigentum an Wert kann übertragen werden durch -

  • Zuweisen des Werts einer Variablen zu einer anderen Variablen.

  • Wert an eine Funktion übergeben.

  • Rückgabewert einer Funktion.

Zuweisen des Werts einer Variablen zu einer anderen Variablen

Das Hauptverkaufsargument von Rust als Sprache ist seine Gedächtnissicherheit. Die Speichersicherheit wird durch eine strenge Kontrolle darüber erreicht, wer was und wann Einschränkungen verwenden kann.

Betrachten Sie das folgende Snippet -

fn main(){
   let v = vec![1,2,3]; 
   // vector v owns the object in heap

   //only a single variable owns the heap memory at any given time
   let v2 = v; 
   // here two variables owns heap value,
   //two pointers to the same content is not allowed in rust

   //Rust is very smart in terms of memory access ,so it detects a race condition
   //as two variables point to same heap

   println!("{:?}",v);
}

Das obige Beispiel deklariert einen Vektor v. Die Idee des Eigentums ist, dass auch nur eine Variable an eine Ressource gebunden ist v bindet an Ressource oder v2bindet an die Ressource. Das obige Beispiel löst einen Fehler aus - Verwendung des verschobenen Werts: `v` . Dies liegt daran, dass das Eigentum an der Ressource auf Version 2 übertragen wird. Dies bedeutet, dass das Eigentum von v nach v2 verschoben wird (v2 = v) und v nach dem Verschieben ungültig wird.

Wert an eine Funktion übergeben

Der Besitz eines Werts ändert sich auch, wenn wir ein Objekt im Heap an einen Abschluss oder eine Funktion übergeben.

fn main(){
   let v = vec![1,2,3];     // vector v owns the object in heap
   let v2 = v;              // moves ownership to v2
   display(v2);             // v2 is moved to display and v2 is invalidated
   println!("In main {:?}",v2);    //v2 is No longer usable here
}
fn display(v:Vec<i32>){
   println!("inside display {:?}",v);
}

Rückgabewert einer Funktion

Das an die Funktion übergebene Eigentum wird ungültig, wenn die Funktionsausführung abgeschlossen ist. Eine Lösung hierfür besteht darin, dass die Funktion das eigene Objekt an den Aufrufer zurückgibt.

fn main(){
   let v = vec![1,2,3];       // vector v owns the object in heap
   let v2 = v;                // moves ownership to v2
   let v2_return = display(v2);    
   println!("In main {:?}",v2_return);
}
fn display(v:Vec<i32>)->Vec<i32> { 
   // returning same vector
   println!("inside display {:?}",v);
}

Eigentum und primitive Typen

Bei primitiven Typen wird der Inhalt einer Variablen in eine andere kopiert. Es findet also kein Eigentümerwechsel statt. Dies liegt daran, dass eine primitive Variable weniger Ressourcen benötigt als ein Objekt. Betrachten Sie das folgende Beispiel -

fn main(){
   let u1 = 10;
   let u2 = u1;  // u1 value copied(not moved) to u2

   println!("u1 = {}",u1);
}

Die Ausgabe wird - 10 sein.

Es ist sehr unpraktisch, den Besitz einer Variablen an eine andere Funktion zu übergeben und dann den Besitz zurückzugeben. Rust unterstützt ein Konzept der Kreditaufnahme, bei dem das Eigentum an einem Wert vorübergehend auf ein Unternehmen übertragen und dann an das ursprüngliche Eigentümerunternehmen zurückgegeben wird.

Betrachten Sie Folgendes:

fn main(){
   // a list of nos
   let v = vec![10,20,30];
   print_vector(v);
   println!("{}",v[0]); // this line gives error
}
fn print_vector(x:Vec<i32>){
   println!("Inside print_vector function {:?}",x);
}

Die Hauptfunktion ruft eine Funktion print_vector () auf . Ein Vektor wird als Parameter an diese Funktion übergeben. Der Besitz des Vektors wird auch von main () an die Funktion print_vector () übergeben . Der obige Code führt zu einem Fehler, wie unten gezeigt, wenn die Funktion main () versucht, auf den Vektor v zuzugreifen .

|  print_vector(v);
|     - value moved here
|  println!("{}",v[0]);
|     ^ value used here after move

Dies liegt daran, dass eine Variable oder ein Wert von der Funktion, der sie ursprünglich gehörte, nicht mehr verwendet werden kann, sobald der Besitz auf eine andere Funktion übertragen wurde.

Was ist Ausleihen?

Wenn eine Funktion für eine Weile vorübergehend die Kontrolle über eine Variable / einen Wert auf eine andere Funktion überträgt, spricht man von Ausleihen. Dies wird erreicht, indem ein Verweis auf die Variable übergeben wird(& var_name)anstatt die Variable / den Wert selbst an die Funktion zu übergeben. Der Besitz der Variablen / des Werts wird auf den ursprünglichen Besitzer der Variablen übertragen, nachdem die Funktion, an die das Steuerelement übergeben wurde, die Ausführung abgeschlossen hat.

fn main(){
   // a list of nos
   let v = vec![10,20,30];
   print_vector(&v); // passing reference
   println!("Printing the value from main() v[0]={}",v[0]);
}
fn print_vector(x:&Vec<i32>){
   println!("Inside print_vector function {:?}",x);
}

Ausgabe

Inside print_vector function [10, 20, 30]
Printing the value from main() v[0] = 10

Veränderbare Referenzen

Eine Funktion kann eine ausgeliehene Ressource mithilfe eines veränderlichen Verweises auf eine solche Ressource ändern . Einer veränderlichen Referenz wird ein Präfix vorangestellt&mut. Veränderbare Referenzen können nur mit veränderlichen Variablen arbeiten.

Abbildung: Mutieren einer Ganzzahlreferenz

fn add_one(e: &mut i32) {
   *e+= 1;
}
fn main() {
   let mut i = 3;
   add_one(&mut i);
   println!("{}", i);
}

Die Funktion main () deklariert eine veränderbare Ganzzahlvariable i und übergibt eine veränderbare Referenz von i an dieadd_one(). Das add_one () erhöht den Wert der Variablen i um eins.

Abbildung: Mutieren einer Zeichenfolgenreferenz

fn main() {
   let mut name:String = String::from("TutorialsPoint");
   display(&mut name); 
   //pass a mutable reference of name
   println!("The value of name after modification is:{}",name);
}
fn display(param_name:&mut String){
   println!("param_name value is :{}",param_name);
   param_name.push_str(" Rocks"); 
   //Modify the actual string,name
}

Die main () Funktion gibt eine veränderliche Referenz des variablen Namen mit dem Display () Funktion. Die Anzeigefunktion fügt eine zusätzliche Zeichenfolge an den ursprünglichen Namen Variable.

Ausgabe

param_name value is :TutorialsPoint
The value of name after modification is:TutorialsPoint Rocks

Ein Slice ist ein Zeiger auf einen Speicherblock. Slices können verwendet werden, um auf Teile von Daten zuzugreifen, die in zusammenhängenden Speicherblöcken gespeichert sind. Es kann mit Datenstrukturen wie Arrays, Vektoren und Strings verwendet werden. Slices verwenden Indexnummern, um auf Teile von Daten zuzugreifen. Die Größe eines Slice wird zur Laufzeit festgelegt.

Slices sind Zeiger auf die tatsächlichen Daten. Sie werden unter Bezugnahme auf Funktionen übergeben, die auch als Ausleihen bezeichnet werden.

Beispielsweise können Slices verwendet werden, um einen Teil eines Zeichenfolgenwerts abzurufen. Eine in Scheiben geschnittene Zeichenfolge ist ein Zeiger auf das tatsächliche Zeichenfolgenobjekt. Daher müssen wir den Start- und Endindex eines Strings angeben. Der Index beginnt wie bei Arrays bei 0.

Syntax

let sliced_value = &data_structure[start_index..end_index]

Der minimale Indexwert ist 0 und der maximale Indexwert ist die Größe der Datenstruktur. HINWEIS, dass der end_index nicht in der endgültigen Zeichenfolge enthalten ist.

Das folgende Diagramm zeigt eine Beispielzeichenfolge für Tutorials mit 9 Zeichen. Der Index des ersten Zeichens ist 0 und der des letzten Zeichens ist 8.

Der folgende Code ruft 5 Zeichen aus der Zeichenfolge ab (beginnend mit Index 4).

fn main() {
   let n1 = "Tutorials".to_string();
   println!("length of string is {}",n1.len());
   let c1 = &n1[4..9]; 
   
   // fetches characters at 4,5,6,7, and 8 indexes
   println!("{}",c1);
}

Ausgabe

length of string is 9
rials

Abbildung - Schneiden eines ganzzahligen Arrays

Die Funktion main () deklariert ein Array mit 5 Elementen. Es ruft dieuse_slice()Funktion und übergibt ihm eine Schicht aus drei Elementen (zeigt auf das Datenarray). Die Scheiben werden als Referenz übergeben. Die Funktion use_slice () gibt den Wert des Slice und seine Länge aus.

fn main(){
   let data = [10,20,30,40,50];
   use_slice(&data[1..4]);
   //this is effectively borrowing elements for a while
}
fn use_slice(slice:&[i32]) { 
   // is taking a slice or borrowing a part of an array of i32s
   println!("length of slice is {:?}",slice.len());
   println!("{:?}",slice);
}

Ausgabe

length of slice is 3
[20, 30, 40]

Mutable Slices

Das &mut Das Schlüsselwort kann verwendet werden, um ein Slice als veränderlich zu markieren.

fn main(){
   let mut data = [10,20,30,40,50];
   use_slice(&mut data[1..4]);
   // passes references of 
   20, 30 and 40
   println!("{:?}",data);
}
fn use_slice(slice:&mut [i32]) {
   println!("length of slice is {:?}",slice.len());
   println!("{:?}",slice);
   slice[0] = 1010; // replaces 20 with 1010
}

Ausgabe

length of slice is 3
[20, 30, 40]
[10, 1010, 30, 40, 50]

Der obige Code übergibt ein veränderbares Slice an die Funktion use_slice () . Die Funktion ändert das zweite Element des ursprünglichen Arrays.

Arrays werden verwendet, um eine homogene Sammlung von Werten darzustellen. In ähnlicher Weise ist eine Struktur ein anderer benutzerdefinierter Datentyp, der in Rust verfügbar ist und es uns ermöglicht, Datenelemente verschiedener Typen, einschließlich einer anderen Struktur, zu kombinieren. Eine Struktur definiert Daten als Schlüssel-Wert-Paar.

Syntax - Deklarieren einer Struktur

Das Schlüsselwort struct wird verwendet, um eine Struktur zu deklarieren. Da Strukturen statisch typisiert sind, muss jedes Feld in der Struktur einem Datentyp zugeordnet werden. Die Namensregeln und Konventionen für eine Struktur ähneln denen einer Variablen. Der Strukturblock muss mit einem Semikolon enden.

struct Name_of_structure {
   field1:data_type,
   field2:data_type,
   field3:data_type
}

Syntax - Initialisieren einer Struktur

Nach dem Deklarieren einer Struktur sollte jedem Feld ein Wert zugewiesen werden. Dies wird als Initialisierung bezeichnet.

let instance_name = Name_of_structure {
   field1:value1,
   field2:value2,
   field3:value3
}; 
//NOTE the semicolon
Syntax: Accessing values in a structure
Use the dot notation to access value of a specific field.
instance_name.field1
Illustration
struct Employee {
   name:String,
   company:String,
   age:u32
}
fn main() {
   let emp1 = Employee {
      company:String::from("TutorialsPoint"),
      name:String::from("Mohtashim"),
      age:50
   };
   println!("Name is :{} company is {} age is {}",emp1.name,emp1.company,emp1.age);
}

Das obige Beispiel deklariert einen strukturierten Mitarbeiter mit drei Feldern - Name, Firma und Alter der Typen. Das main () initialisiert die Struktur. Es verwendet den Druck! Makro zum Drucken der Werte der in der Struktur definierten Felder.

Ausgabe

Name is :Mohtashim company is TutorialsPoint age is 50

Ändern einer Strukturinstanz

Um eine Instanz zu ändern, sollte die Instanzvariable als veränderbar markiert werden. Das folgende Beispiel deklariert und initialisiert eine Struktur namens Mitarbeiter und später modifiziert Wert des Altersfeldes auf 40 von 50.

let mut emp1 = Employee {
   company:String::from("TutorialsPoint"),
   name:String::from("Mohtashim"),
   age:50
};
emp1.age = 40;
println!("Name is :{} company is {} age is 
{}",emp1.name,emp1.company,emp1.age);

Ausgabe

Name is :Mohtashim company is TutorialsPoint age is 40

Übergeben einer Struktur an eine Funktion

Das folgende Beispiel zeigt, wie eine Instanz von struct als Parameter übergeben wird. Die Anzeigemethode verwendet eine Employee-Instanz als Parameter und druckt die Details.

fn display( emp:Employee) {
   println!("Name is :{} company is {} age is 
   {}",emp.name,emp.company,emp.age);
}

Hier ist das komplette Programm -

//declare a structure
struct Employee {
   name:String,
   company:String,
   age:u32
}
fn main() {
   //initialize a structure
   let emp1 = Employee {
      company:String::from("TutorialsPoint"),
      name:String::from("Mohtashim"),
      age:50
   };
   let emp2 = Employee{
      company:String::from("TutorialsPoint"),
      name:String::from("Kannan"),
      age:32
   };
   //pass emp1 and emp2 to display()
   display(emp1);
   display(emp2);
}
// fetch values of specific structure fields using the 
// operator and print it to the console
fn display( emp:Employee){
   println!("Name is :{} company is {} age is 
   {}",emp.name,emp.company,emp.age);
}

Ausgabe

Name is :Mohtashim company is TutorialsPoint age is 50
Name is :Kannan company is TutorialsPoint age is 32

Struktur von einer Funktion zurückgeben

Betrachten wir eine Funktion who_is_elder () , die das Alter von zwei Mitarbeitern vergleicht und die ältere zurückgibt.

fn who_is_elder (emp1:Employee,emp2:Employee)->Employee {
   if emp1.age>emp2.age {
      return emp1;
   } else {
      return emp2;
   }
}

Hier ist das komplette Programm -

fn main() {
   //initialize structure
   let emp1 = Employee{
      company:String::from("TutorialsPoint"),
      name:String::from("Mohtashim"),
      age:50
   };
   let emp2 = Employee {
      company:String::from("TutorialsPoint"),
      name:String::from("Kannan"),
      age:32
   };
   let elder = who_is_elder(emp1,emp2);
   println!("elder is:");

   //prints details of the elder employee
   display(elder);
}
//accepts instances of employee structure and compares their age
fn who_is_elder (emp1:Employee,emp2:Employee)->Employee {
   if emp1.age>emp2.age {
      return emp1;
   } else {
      return emp2;
   }
}
//display name, comapny and age of the employee
fn display( emp:Employee) {
   println!("Name is :{} company is {} age is {}",emp.name,emp.company,emp.age);
}
//declare a structure
struct Employee {
   name:String,
   company:String,
   age:u32
}

Ausgabe

elder is:
Name is :Mohtashim company is TutorialsPoint age is 50

Methode in der Struktur

Methoden sind wie Funktionen. Sie sind eine logische Gruppe von Programmieranweisungen. Methoden werden mit dem deklariertfnStichwort. Der Umfang einer Methode liegt innerhalb des Strukturblocks.

Methoden werden außerhalb des Strukturblocks deklariert. DasimplMit dem Schlüsselwort wird eine Methode im Kontext einer Struktur definiert. Der erste Parameter einer Methode ist immerself, die die aufrufende Instanz der Struktur darstellt. Methoden arbeiten mit den Datenelementen einer Struktur.

Um eine Methode aufzurufen, müssen wir zuerst die Struktur instanziieren. Die Methode kann über die Instanz der Struktur aufgerufen werden.

Syntax

struct My_struct {}
impl My_struct { 
   //set the method's context
   fn method_name() { 
      //define a method
   }
}

Illustration

Das folgende Beispiel definiert eine Struktur Rechteck mit Feldern - Breite und Höhe . Verfahren Bereich ist innerhalb der Struktur des Kontextes definiert. Die area-Methode greift über das Schlüsselwort self auf die Felder der Struktur zu und berechnet die Fläche eines Rechtecks.

//define dimensions of a rectangle
struct Rectangle {
   width:u32, height:u32
}

//logic to calculate area of a rectangle
impl Rectangle {
   fn area(&self)->u32 {
      //use the . operator to fetch the value of a field via the self keyword
      self.width * self.height
   }
}

fn main() {
   // instanatiate the structure
   let small = Rectangle {
      width:10,
      height:20
   };
   //print the rectangle's area
   println!("width is {} height is {} area of Rectangle 
   is {}",small.width,small.height,small.area());
}

Ausgabe

width is 10 height is 20 area of Rectangle is 200

Statische Methode in der Struktur

Statische Methoden können als Dienstprogrammmethoden verwendet werden. Diese Methoden existieren bereits, bevor die Struktur instanziiert wird. Statische Methoden werden unter Verwendung des Strukturnamens aufgerufen und können ohne Instanz aufgerufen werden. Im Gegensatz zu normalen Methoden akzeptiert eine statische Methode nicht den Parameter & self .

Syntax - Deklarieren einer statischen Methode

Eine statische Methode wie Funktionen und andere Methoden kann optional Parameter enthalten.

impl Structure_Name {
   //static method that creates objects of the Point structure
   fn method_name(param1: datatype, param2: datatype) -> return_type {
      // logic goes here
   }
}

Syntax - Aufrufen einer statischen Methode

Die Struktur_Name :: -Syntax wird verwendet, um auf eine statische Methode zuzugreifen.

structure_name::method_name(v1,v2)

Illustration

Im folgenden Beispiel wird die Methode getInstance als Factory-Klasse verwendet, die Instanzen der Struktur Point erstellt und zurückgibt .

//declare a structure
struct Point {
   x: i32,
   y: i32,
}
impl Point {
   //static method that creates objects of the Point structure
   fn getInstance(x: i32, y: i32) -> Point {
      Point { x: x, y: y }
   }
   //display values of the structure's field
   fn display(&self){
      println!("x ={} y={}",self.x,self.y );
   }
}
fn main(){
   // Invoke the static method
   let p1 = Point::getInstance(10,20);
   p1.display();
}

Ausgabe

x =10 y=20

Wenn wir in der Rust-Programmierung einen Wert aus einer Liste möglicher Varianten auswählen müssen, verwenden wir Aufzählungsdatentypen. Ein Aufzählungstyp wird mit dem Schlüsselwort enum deklariert. Es folgt die Syntax von enum -

enum enum_name {
   variant1,
   variant2,
   variant3
}

Abbildung: Verwenden einer Aufzählung

Das Beispiel deklariert eine Aufzählung - GenderCategory , die Varianten als männlich und weiblich hat. Der Druck! Makro zeigt den Wert der Aufzählung an. Der Compiler gibt einen Fehler aus. Das Merkmal std :: fmt :: Debug ist für GenderCategory nicht implementiert . Das Attribut # [ableiten (Debuggen]] wird verwendet, um diesen Fehler zu unterdrücken.

// The `derive` attribute automatically creates the implementation
// required to make this `enum` printable with `fmt::Debug`.
#[derive(Debug)]
enum GenderCategory {
   Male,Female
}
fn main() {
   let male = GenderCategory::Male;
   let female = GenderCategory::Female;

   println!("{:?}",male);
   println!("{:?}",female);
}

Ausgabe

Male
Female

Struktur und Aufzählung

Das folgende Beispiel definiert eine Struktur Person. Das Feld Geschlecht ist vom Typ GenderCategory (eine Aufzählung) und kann als Wert entweder männlich oder weiblich zugewiesen werden.

// The `derive` attribute automatically creates the 
implementation
// required to make this `enum` printable with 
`fmt::Debug`.

#[derive(Debug)]
enum GenderCategory {
   Male,Female
}

// The `derive` attribute automatically creates the implementation
// required to make this `struct` printable with `fmt::Debug`.
#[derive(Debug)]
struct Person {
   name:String,
   gender:GenderCategory
}

fn main() {
   let p1 = Person {
      name:String::from("Mohtashim"),
      gender:GenderCategory::Male
   };
   let p2 = Person {
      name:String::from("Amy"),
      gender:GenderCategory::Female
   };
   println!("{:?}",p1);
   println!("{:?}",p2);
}

Im Beispiel werden die Objekte p1 und p2 vom Typ Person erstellt und die Attribute, der Name und das Geschlecht für jedes dieser Objekte initialisiert.

Ausgabe

Person { name: "Mohtashim", gender: Male }
Person { name: "Amy", gender: Female }

Option Enum

Option ist eine vordefinierte Aufzählung in der Rust-Standardbibliothek. Diese Aufzählung hat zwei Werte - Einige (Daten) und Keine.

Syntax

enum Option<T> {
   Some(T),      //used to return a value
   None          // used to return null, as Rust doesn't support 
   the null keyword
}

Hier repräsentiert der Typ T einen Wert eines beliebigen Typs.

Rust unterstützt das Schlüsselwort null nicht . Der Wert None in enumOption kann von einer Funktion verwendet werden, um einen Nullwert zurückzugeben. Wenn Daten zurückgegeben werden müssen, kann die Funktion Some (Daten) zurückgeben .

Lassen Sie uns dies anhand eines Beispiels verstehen -

Das Programm definiert eine Funktion is_even () mit einem Rückgabetyp Option. Die Funktion überprüft, ob der übergebene Wert eine gerade Zahl ist. Wenn die Eingabe gerade ist, wird ein Wert true zurückgegeben, andernfalls gibt die Funktion None zurück .

fn main() {
   let result = is_even(3);
   println!("{:?}",result);
   println!("{:?}",is_even(30));
}
fn is_even(no:i32)->Option<bool> {
   if no%2 == 0 {
      Some(true)
   } else {
      None
   }
}

Ausgabe

None
Some(true)

Match Statement und Enum

Die match- Anweisung kann verwendet werden, um in einer Aufzählung gespeicherte Werte zu vergleichen. Das folgende Beispiel definiert eine Funktion, print_size , die die CarType-Enumeration als Parameter verwendet. Die Funktion vergleicht die Parameterwerte mit einem vordefinierten Satz von Konstanten und zeigt die entsprechende Meldung an.

enum CarType {
   Hatch,
   Sedan,
   SUV
}
fn print_size(car:CarType) {
   match car {
      CarType::Hatch => {
         println!("Small sized car");
      },
      CarType::Sedan => {
         println!("medium sized car");
      },
      CarType::SUV =>{
         println!("Large sized Sports Utility car");
      }
   }
}
fn main(){
   print_size(CarType::SUV);
   print_size(CarType::Hatch);
   print_size(CarType::Sedan);
}

Ausgabe

Large sized Sports Utility car
Small sized car
medium sized car

Mit Option abgleichen

Das Beispiel der Funktion is_even , die den Optionstyp zurückgibt, kann auch mit der Anweisung match implementiert werden, wie unten gezeigt -

fn main() {
   match is_even(5) {
      Some(data) => {
         if data==true {
            println!("Even no");
         }
      },
      None => {
         println!("not even");
      }
   }
}
fn is_even(no:i32)->Option<bool> {
   if no%2 == 0 {
      Some(true)
   } else {
      None
   }
}

Ausgabe

not even

Match & Enum mit Datentyp

Es ist möglich, jeder Variante einer Aufzählung einen Datentyp hinzuzufügen. Im folgenden Beispiel sind die Varianten Name und Usr_ID der Aufzählung vom Typ String bzw. Integer. Das folgende Beispiel zeigt die Verwendung einer Übereinstimmungsanweisung mit einer Aufzählung mit einem Datentyp.

// The `derive` attribute automatically creates the implementation
// required to make this `enum` printable with `fmt::Debug`.
#[derive(Debug)]
enum GenderCategory {
   Name(String),Usr_ID(i32)
}
fn main() {
   let p1 = GenderCategory::Name(String::from("Mohtashim"));
   let p2 = GenderCategory::Usr_ID(100);
   println!("{:?}",p1);
   println!("{:?}",p2);

   match p1 {
      GenderCategory::Name(val)=> {
         println!("{}",val);
      }
      GenderCategory::Usr_ID(val)=> {
         println!("{}",val);
      }
   }
}

Ausgabe

Name("Mohtashim")
Usr_ID(100)
Mohtashim

Eine logische Gruppe von Code wird als Modul bezeichnet. Mehrere Module werden zu einer Einheit namens zusammengefasstcrate. Rostprogramme können eine Binärkiste oder eine Bibliothekskiste enthalten. Eine binäre Kiste ist ein ausführbares Projekt mit einer main () -Methode. Eine Bibliothekskiste ist eine Gruppe von Komponenten, die in anderen Projekten wiederverwendet werden können. Im Gegensatz zu einer binären Kiste hat eine Bibliothekskiste keinen Einstiegspunkt (main () -Methode). Mit dem Cargo-Tool können Sie Kisten in Rust verwalten. Beispielsweise enthält das Netzwerkmodul netzwerkbezogene Funktionen und das Grafikmodul zeichnungsbezogene Funktionen. Module ähneln Namespaces in anderen Programmiersprachen. Kisten von Drittanbietern können mit Fracht von crates.io heruntergeladen werden .

Sr.Nr. Begriff & Beschreibung
1

crate

Ist eine Zusammenstellungseinheit in Rust; Crate wird als Binärdatei oder Bibliothek kompiliert.

2

cargo

Das offizielle Rust Package Management Tool für Kisten.

3

module

Gruppiert logisch Code in einer Kiste.

4

crates.io

Die offizielle Rust-Paketregistrierung.

Syntax

//public module
pub mod a_public_module {
   pub fn a_public_function() {
      //public function
   }
   fn a_private_function() {
      //private function
   }
}
//private module
mod a_private_module {
   fn a_private_function() {
   }
}

Module können öffentlich oder privat sein. Auf Komponenten in einem privaten Modul können andere Module nicht zugreifen. Module in Rust sind standardmäßig privat. Im Gegensatz dazu können andere Module auf Funktionen in einem öffentlichen Modul zugreifen. Modulen sollte ein Präfix vorangestellt werdenpubSchlüsselwort, um es öffentlich zu machen. Funktionen innerhalb eines öffentlichen Moduls müssen ebenfalls veröffentlicht werden.

Abbildung: Definieren eines Moduls

Das Beispiel definiert ein öffentliches Modul - Filme . Das Modul enthält eine Funktion play () , die einen Parameter akzeptiert und dessen Wert druckt.

pub mod movies {
   pub fn play(name:String) {
      println!("Playing movie {}",name);
   }
}
fn main(){
   movies::play("Herold and Kumar".to_string());
}

Ausgabe

Playing movie Herold and Kumar

Schlüsselwort verwenden

Das Schlüsselwort use hilft beim Importieren eines öffentlichen Moduls.

Syntax

use public_module_name::function_name;

Illustration

pub mod movies {
   pub fn play(name:String) {
      println!("Playing movie {}",name);
   }
}
use movies::play;
fn main(){
   play("Herold and Kumar ".to_string());
}

Ausgabe

Playing movie Herold and Kumar

Verschachtelte Module

Module können auch verschachtelt werden. Das Comedy - Modul innerhalb des verschachtelten Englisch - Modul, das weiter in dem verschachtelt ist Filme Modul. Das folgende Beispiel definiert eine Funktion gegeben Spiel innerhalb der Filme / Englisch / Comedy - Modul.

pub mod movies {
   pub mod english {
      pub mod comedy {
         pub fn play(name:String) {
            println!("Playing comedy movie {}",name);
         }
      }
   }
}
use movies::english::comedy::play; 
// importing a public module

fn main() {
   // short path syntax
   play("Herold and Kumar".to_string());
   play("The Hangover".to_string());

   //full path syntax
   movies::english::comedy::play("Airplane!".to_string());
}

Ausgabe

Playing comedy movie Herold and Kumar
Playing comedy movie The Hangover
Playing comedy movie Airplane!

Abbildung - Erstellen Sie eine Bibliothekskiste und verbrauchen Sie sie in einer Binärkiste

Lassen Sie uns eine Bibliothekskiste mit dem Namen erstellen movie_lib, die ein Modul enthält movies. Um das zu bauenmovie_lib Bibliothek Kiste, werden wir das Tool verwenden cargo.

Schritt 1 - Projektordner erstellen

Erstellen Sie eine Ordner- Film-App, gefolgt von einem Unterordner movie-lib . Nachdem der Ordner und der Unterordner erstellt wurden, erstellen Sie einesrcOrdner und eine Cargo.toml-Datei in diesem Verzeichnis. Der Quellcode sollte sich im Ordner src befinden . Erstellen Sie die Dateien lib.rs und movies.rs im Ordner src. Die Datei Cargo.toml enthält die Metadaten des Projekts wie Versionsnummer, Autorenname usw.

Die Projektverzeichnisstruktur sieht wie folgt aus:

movie-app
   movie-lib/
      -->Cargo.toml
      -->src/
         lib.rs
         movies.rs

Schritt 2 - Bearbeiten Sie die Datei Cargo.toml, um Projektmetadaten hinzuzufügen

[package]
name = "movies_lib"
version = "0.1.0"
authors = ["Mohtashim"]

Schritt 3 - Bearbeiten Sie die Datei lib.rs.

Fügen Sie dieser Datei die folgende Moduldefinition hinzu.

pub mod movies;

Die obige Zeile erstellt ein öffentliches Modul - movies.

Schritt 4 - Bearbeiten Sie die Datei movies.rs

Diese Datei definiert alle Funktionen für das Filmmodul.

pub fn play(name:String){
   println!("Playing movie {} :movies-app",name);
}

Der obige Code definiert eine Funktion play() das akzeptiert einen Parameter und druckt ihn auf der Konsole.

Schritt 5 - Erstellen Sie die Bibliothekskiste

Erstellen Sie eine App mit dem cargo buildBefehl, um zu überprüfen, ob die Bibliothekskiste richtig strukturiert ist. Stellen Sie sicher, dass Sie sich im Stammverzeichnis des Projekts befinden - dem Film-App-Ordner. Die folgende Meldung wird im Terminal angezeigt, wenn der Build erfolgreich war.

D:\Rust\movie-lib> cargo build
   Compiling movies_lib v0.1.0 (file:///D:/Rust/movie-lib)
   Finished dev [unoptimized + debuginfo] target(s) in 0.67s

Schritt 6 - Erstellen Sie eine Testanwendung

Erstellen Sie einen anderen Ordner movie-lib-testim Film-App-Ordner, gefolgt von einer Cargo.toml-Datei und dem src-Ordner. Dieses Projekt sollte eine Hauptmethode haben, da dies eine binäre Kiste ist, die die zuvor erstellte Bibliothekskiste verbraucht. Erstellen Sie eine main.rs-Datei im Ordner src. Die Ordnerstruktur ist wie gezeigt.

movie-app
   movie-lib 
   // already completed

   movie-lib-test/
      -->Cargo.toml
      -->src/
         main.rs

Schritt 7 - Fügen Sie Folgendes in die Datei Cargo.toml ein

[package]
name = "test_for_movie_lib"
version = "0.1.0"
authors = ["Mohtashim"]

[dependencies]
movies_lib = { path = "../movie-lib" }

NOTE- Der Pfad zum Bibliotheksordner wird als Abhängigkeiten festgelegt. Das folgende Diagramm zeigt den Inhalt beider Projekte.

Schritt 8 - Fügen Sie der Datei main.rs Folgendes hinzu

extern crate movies_lib;
use movies_lib::movies::play;
fn main() {
   println!("inside main of test ");
   play("Tutorialspoint".to_string())
}

Der obige Code importiert ein externes Paket namens movies_lib. Überprüfen Sie die Cargo.toml des aktuellen Projekts, um den Namen der Kiste zu überprüfen.

Schritt 9 - Verwendung von Frachtaufbau und Frachtlauf

Wir werden den Frachtbau und den Frachtlauf verwenden, um das Binärprojekt zu erstellen und es wie unten gezeigt auszuführen -

Die Standard-Sammlungsbibliothek von Rust bietet eine effiziente Implementierung der gängigsten allgemeinen Programmierdatenstrukturen. In diesem Kapitel wird die Implementierung der häufig verwendeten Sammlungen - Vector, HashMap und HashSet - erläutert.

Vektor

Ein Vektor ist ein Array mit veränderbarer Größe. Es speichert Werte in zusammenhängenden Speicherblöcken. Die vordefinierte Struktur Vec kann zum Erstellen von Vektoren verwendet werden. Einige wichtige Merkmale eines Vektors sind -

  • Ein Vektor kann zur Laufzeit wachsen oder schrumpfen.

  • Ein Vektor ist eine homogene Sammlung.

  • Ein Vektor speichert Daten als Folge von Elementen in einer bestimmten Reihenfolge. Jedem Element in einem Vektor wird eine eindeutige Indexnummer zugewiesen. Der Index beginnt bei 0 und geht bis zu n-1, wobei n die Größe der Sammlung ist. In einer Sammlung von 5 Elementen befindet sich beispielsweise das erste Element am Index 0 und das letzte Element am Index 4.

  • Ein Vektor hängt nur Werte an (oder nahe) das Ende an. Mit anderen Worten kann ein Vektor verwendet werden, um einen Stapel zu implementieren.

  • Der Speicher für einen Vektor wird im Heap zugewiesen.

Syntax - Erstellen eines Vektors

let mut instance_name = Vec::new();

Mit der statischen Methode new () der Vec- Struktur wird eine Vektorinstanz erstellt.

Alternativ kann mit dem vec auch ein Vektor erstellt werden! Makro. Die Syntax ist wie folgt -

let vector_name = vec![val1,val2,val3]

In der folgenden Tabelle sind einige häufig verwendete Funktionen der Vec-Struktur aufgeführt.

Sr.Nr. Methode Unterschrift & Beschreibung
1 Neu()

pub fn new()->Vect

Konstruiert einen neuen, leeren Vec. Der Vektor wird erst zugeordnet, wenn Elemente darauf verschoben werden.

2 drücken()

pub fn push(&mut self, value: T)

Hängt ein Element an die Rückseite einer Sammlung an.

3 entfernen()

pub fn remove(&mut self, index: usize) -> T

Entfernt das Element am Positionsindex innerhalb des Vektors und gibt es zurück, wobei alle Elemente danach nach links verschoben werden.

4 enthält ()

pub fn contains(&self, x: &T) -> bool

Gibt true zurück, wenn das Slice ein Element mit dem angegebenen Wert enthält.

5 len ()

pub fn len(&self) -> usize

Gibt die Anzahl der Elemente im Vektor zurück, die auch als "Länge" bezeichnet wird.

Abbildung: Erstellen eines Vektors - new ()

Um einen Vektor zu erstellen, verwenden wir die statische Methode new -

fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);

   println!("size of vector is :{}",v.len());
   println!("{:?}",v);
}

Im obigen Beispiel wird ein Vektor mit der statischen Methode new () erstellt , die in der Struktur Vec definiert ist . Die Funktion push (val) hängt den als Parameter übergebenen Wert an die Auflistung an. Die Funktion len () gibt die Länge des Vektors zurück.

Ausgabe

size of vector is :3
[20, 30, 40]

Illustration: Erstellen eines Vektors - vec! Makro

Der folgende Code erstellt einen Vektor mit dem vec! Makro. Dem Datentyp des Vektors wird der erste ihm zugewiesene Wert abgeleitet.

fn main() {
   let v = vec![1,2,3];
   println!("{:?}",v);
}

Ausgabe

[1, 2, 3]

Wie bereits erwähnt, kann ein Vektor nur Werte desselben Datentyps enthalten. Das folgende Snippet löst einen Fehler aus [E0308]: Fehler bei nicht übereinstimmenden Typen .

fn main() {
   let v = vec![1,2,3,"hello"];
   println!("{:?}",v);
}

Abbildung: push ()

Hängt ein Element an das Ende einer Sammlung an.

fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);
   
   println!("{:?}",v);
}

Ausgabe

[20, 30, 40]

Abbildung: entfernen ()

Entfernt das Element am Positionsindex innerhalb des Vektors und gibt es zurück, wobei alle Elemente danach nach links verschoben werden.

fn main() {
   let mut v = vec![10,20,30];
   v.remove(1);
   println!("{:?}",v);
}

Ausgabe

[10, 30]

Abbildung - enthält ()

Gibt true zurück, wenn das Slice ein Element mit dem angegebenen Wert enthält -

fn main() {
   let v = vec![10,20,30];
   if v.contains(&10) {
      println!("found 10");
   }
   println!("{:?}",v);
}

Ausgabe

found 10
[10, 20, 30]

Abbildung: len ()

Gibt die Anzahl der Elemente im Vektor zurück, die auch als "Länge" bezeichnet wird.

fn main() {
   let v = vec![1,2,3];
   println!("size of vector is :{}",v.len());
}

Ausgabe

size of vector is :3

Zugriff auf Werte von einem Vektor

Auf einzelne Elemente in einem Vektor kann mit ihren entsprechenden Indexnummern zugegriffen werden. Im folgenden Beispiel wird eine Vektoranzeige erstellt, die den Wert des ersten Elements druckt.

fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);

   println!("{:?}",v[0]);
}
Output: `20`

Werte in einem Vektor können auch unter Bezugnahme auf die Sammlung abgerufen werden.

fn main() {
   let mut v = Vec::new();
   v.push(20);
   v.push(30);
   v.push(40);
   v.push(500);

   for i in &v {
      println!("{}",i);
   }
   println!("{:?}",v);
}

Ausgabe

20
30
40
500
[20, 30, 40, 500]

HashMap

Eine Karte ist eine Sammlung von Schlüssel-Wert-Paaren (sogenannte Einträge). Keine zwei Einträge in einer Karte können denselben Schlüssel haben. Kurz gesagt, eine Karte ist eine Nachschlagetabelle. Eine HashMap speichert die Schlüssel und Werte in einer Hash-Tabelle. Die Einträge werden in beliebiger Reihenfolge gespeichert. Der Schlüssel wird verwendet, um in der HashMap nach Werten zu suchen. Die HashMap-Struktur ist in der definiertstd::collectionsModul. Dieses Modul sollte explizit importiert werden, um auf die HashMap-Struktur zuzugreifen.

Syntax: Erstellen einer HashMap

let mut instance_name = HashMap::new();

Die statische Methode new () der HashMap- Struktur wird zum Erstellen eines HashMap-Objekts verwendet. Diese Methode erstellt eine leere HashMap.

Die häufig verwendeten Funktionen von HashMap werden nachfolgend erläutert.

Sr.Nr. Methode Unterschrift & Beschreibung
1 einfügen()

pub fn insert(&mut self, k: K, v: V) -> Option

Fügt ein Schlüssel / Wert-Paar ein. Wenn kein Schlüssel vorhanden ist, wird None zurückgegeben. Nach der Aktualisierung wird der alte Wert zurückgegeben.

2 len ()

pub fn len(&self) -> usize

Gibt die Anzahl der Elemente in der Karte zurück.

3 bekommen()

pub fn get<Q: ?Sized>(&lself, k: &Q) -> Option<&V> where K:Borrow Q:Hash+ Eq

Gibt einen Verweis auf den Wert zurück, der dem Schlüssel entspricht.

4 iter ()

pub fn iter(&self) -> Iter<K, V>

Ein Iterator, der alle Schlüssel-Wert-Paare in beliebiger Reihenfolge besucht. Der Iteratorelementtyp ist (& 'a K, &' a V).

5 enthält_schlüssel

pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool

Gibt true zurück, wenn die Map einen Wert für den angegebenen Schlüssel enthält.

6 entfernen()

pub fn remove_entry<Q: ?Sized>(&mut self, k: &Q) -> Option<(K, V)>

Entfernt einen Schlüssel aus der Karte und gibt den gespeicherten Schlüssel und Wert zurück, wenn sich der Schlüssel zuvor in der Karte befand.

Abbildung: Einfügen ()

Fügt ein Schlüssel / Wert-Paar in die HashMap ein.

use std::collections::HashMap;
fn main(){
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");
   println!("{:?}",stateCodes);
}

Das obige Programm erstellt eine HashMap und initialisiert sie mit 2 Schlüssel-Wert-Paaren.

Ausgabe

{"KL": "Kerala", "MH": "Maharashtra"}

Abbildung: len ()

Gibt die Anzahl der Elemente in der Karte zurück

use std::collections::HashMap;
fn main() {
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");
   println!("size of map is {}",stateCodes.len());
}

Im obigen Beispiel wird eine HashMap erstellt und die Gesamtzahl der darin enthaltenen Elemente gedruckt.

Ausgabe

size of map is 2

Illustration - get ()

Gibt einen Verweis auf den Wert zurück, der dem Schlüssel entspricht. Im folgenden Beispiel wird der Wert für den Schlüssel KL in der HashMap abgerufen .

use std::collections::HashMap;
fn main() {
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");
   println!("size of map is {}",stateCodes.len());
   println!("{:?}",stateCodes);

   match stateCodes.get(&"KL") {
      Some(value)=> {
         println!("Value for key KL is {}",value);
      }
      None => {
         println!("nothing found");
      }
   }
}

Ausgabe

size of map is 2
{"KL": "Kerala", "MH": "Maharashtra"}
Value for key KL is Kerala

Illustration - iter ()

Gibt einen Iterator zurück, der in einer beliebigen Reihenfolge auf alle Schlüssel-Wert-Paare verweist.

use std::collections::HashMap;
fn main() {
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");

   for (key, val) in stateCodes.iter() {
      println!("key: {} val: {}", key, val);
   }
}

Ausgabe

key: MH val: Maharashtra
key: KL val: Kerala

Abbildung: enthält_key ()

Gibt true zurück, wenn die Map einen Wert für den angegebenen Schlüssel enthält.

use std::collections::HashMap;
fn main() {
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");
   stateCodes.insert("GJ","Gujarat");

   if stateCodes.contains_key(&"GJ") {
      println!("found key");
   }
}

Ausgabe

found key

Abbildung: entfernen ()

Entfernt einen Schlüssel von der Karte.

use std::collections::HashMap;
fn main() {
   let mut stateCodes = HashMap::new();
   stateCodes.insert("KL","Kerala");
   stateCodes.insert("MH","Maharashtra");
   stateCodes.insert("GJ","Gujarat");

   println!("length of the hashmap {}",stateCodes.len());
   stateCodes.remove(&"GJ");
   println!("length of the hashmap after remove() {}",stateCodes.len());
}

Ausgabe

length of the hashmap 3
length of the hashmap after remove() 2

HashSet

HashSet ist eine Menge eindeutiger Werte vom Typ T. Das Hinzufügen und Entfernen von Werten ist schnell und es ist schnell zu fragen, ob ein bestimmter Wert in der Menge enthalten ist oder nicht. Die HashSet-Struktur wird im Modul std :: collection definiert. Dieses Modul sollte explizit importiert werden, um auf die HashSet-Struktur zuzugreifen.

Syntax: Erstellen eines HashSets

let mut hash_set_name = HashSet::new();

Die neue statische Methode der HashSet-Struktur wird zum Erstellen eines HashSet verwendet. Diese Methode erstellt ein leeres HashSet.

In der folgenden Tabelle sind einige der häufig verwendeten Methoden der HashSet-Struktur aufgeführt.

Sr.Nr. Methode Unterschrift & Beschreibung
1 einfügen()

pub fn insert(&mut self, value: T) -> bool

Fügt dem Satz einen Wert hinzu. Wenn in der Gruppe dieser Wert nicht vorhanden war, wird true zurückgegeben, andernfalls false.

2 len ()

pub fn len(&self) -> usize

Gibt die Anzahl der Elemente in der Menge zurück.

3 bekommen()

pub fn get<Q:?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow,Q: Hash + Eq,

Gibt einen Verweis auf den Wert in der Menge zurück, falls einer dem angegebenen Wert entspricht.

4 iter ()

pub fn iter(&self) -> Iter

Gibt einen Iterator zurück, der alle Elemente in beliebiger Reihenfolge besucht. Der Iteratorelementtyp ist & 'a T.

5 enthält_schlüssel

pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool

Gibt true zurück, wenn die Menge einen Wert enthält.

6 entfernen()

pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool

Entfernt einen Wert aus dem Satz. Gibt true zurück, wenn der Wert in der Menge vorhanden war.

Abbildung - einfügen ()

Fügt dem Satz einen Wert hinzu. Ein HashSet fügt der Sammlung keine doppelten Werte hinzu.

use std::collections::HashSet;
fn main() {
   let mut names = HashSet::new();

   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");
   names.insert("Mohtashim");//duplicates not added

   println!("{:?}",names);
}

Ausgabe

{"TutorialsPoint", "Kannan", "Mohtashim"}

Abbildung: len ()

Gibt die Anzahl der Elemente in der Menge zurück.

use std::collections::HashSet;
fn main() {
   let mut names = HashSet::new();
   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");
   println!("size of the set is {}",names.len());
}

Ausgabe

size of the set is 3

Illustration - iter ()

Führt einen Iterator erneut aus, der alle Elemente in beliebiger Reihenfolge besucht.

use std::collections::HashSet;
fn main() {
   let mut names = HashSet::new();
   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");
   names.insert("Mohtashim");

   for name in names.iter() {
      println!("{}",name);
   }
}

Ausgabe

TutorialsPoint
Mohtashim
Kannan

Illustration: get ()

Gibt einen Verweis auf den Wert in der Menge zurück, falls vorhanden, der dem angegebenen Wert entspricht.

use std::collections::HashSet;
fn main() {
   let mut names = HashSet::new();
   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");
   names.insert("Mohtashim");

   match names.get(&"Mohtashim"){
      Some(value)=>{
         println!("found {}",value);
      }
      None =>{
         println!("not found");
      }
   }
   println!("{:?}",names);
}

Ausgabe

found Mohtashim
{"Kannan", "Mohtashim", "TutorialsPoint"}

Abbildung - enthält ()

Gibt true zurück, wenn die Menge einen Wert enthält.

use std::collections::HashSet;

fn main() {
   let mut names = HashSet::new();
   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");

   if names.contains(&"Kannan") {
      println!("found name");
   }  
}

Ausgabe

found name

Abbildung: entfernen ()

Entfernt einen Wert aus dem Satz.

use std::collections::HashSet;

fn main() {
   let mut names = HashSet::new();
   names.insert("Mohtashim");
   names.insert("Kannan");
   names.insert("TutorialsPoint");
   println!("length of the Hashset: {}",names.len());
   names.remove(&"Kannan");
   println!("length of the Hashset after remove() : {}",names.len());
}

Ausgabe

length of the Hashset: 3
length of the Hashset after remove() : 2

In Rust können Fehler in zwei Hauptkategorien eingeteilt werden, wie in der folgenden Tabelle gezeigt.

Sr.Nr. Name & Beschreibung Verwendung
1

Recoverable

Fehler, die behandelt werden können

Ergebnisaufzählung
2

UnRecoverable

Fehler, die nicht behandelt werden können

Panikmakro

Ein behebbarer Fehler ist ein Fehler, der behoben werden kann. Ein Programm kann den fehlgeschlagenen Vorgang wiederholen oder eine alternative Vorgehensweise angeben, wenn ein behebbarer Fehler auftritt. Behebbare Fehler führen nicht dazu, dass ein Programm abrupt fehlschlägt. Ein Beispiel für einen behebbaren Fehler ist der Fehler " Datei nicht gefunden" .

Nicht behebbare Fehler führen dazu, dass ein Programm abrupt fehlschlägt. Ein Programm kann nicht in den Normalzustand zurückkehren, wenn ein nicht behebbarer Fehler auftritt. Der fehlgeschlagene Vorgang kann nicht wiederholt oder der Fehler rückgängig gemacht werden. Ein Beispiel für einen nicht behebbaren Fehler ist der Versuch, auf einen Speicherort jenseits des Endes eines Arrays zuzugreifen.

Im Gegensatz zu anderen Programmiersprachen hat Rust keine Ausnahmen. Es gibt ein Enum- Ergebnis <T, E> für behebbare Fehler zurück, während es das aufruftpanicMakro, wenn das Programm auf einen nicht behebbaren Fehler stößt. Die Panik Makro bewirkt , dass das Programm abrupt zu verlassen.

Panikmakro und nicht behebbare Fehler

Panik! Mit dem Makro kann ein Programm sofort beendet und dem Aufrufer des Programms eine Rückmeldung gegeben werden. Es sollte verwendet werden, wenn ein Programm einen nicht wiederherstellbaren Zustand erreicht.

fn main() {
   panic!("Hello");
   println!("End of main"); //unreachable statement
}

Im obigen Beispiel wird das Programm sofort beendet, wenn es auf Panik stößt ! Makro.

Ausgabe

thread 'main' panicked at 'Hello', main.rs:3

Illustration: Panik! Makro

fn main() {
   let a = [10,20,30];
   a[10]; //invokes a panic since index 10 cannot be reached
}

Die Ausgabe ist wie unten gezeigt -

warning: this expression will panic at run-time
--> main.rs:4:4
  |
4 | a[10];
  | ^^^^^ index out of bounds: the len is 3 but the index is 10

$main
thread 'main' panicked at 'index out of bounds: the len 
is 3 but the index is 10', main.rs:4
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Ein Programm kann die Panik auslösen! Makro, wenn Geschäftsregeln verletzt werden, wie im folgenden Beispiel gezeigt -

fn main() {
   let no = 13; 
   //try with odd and even
   if no%2 == 0 {
      println!("Thank you , number is even");
   } else {
      panic!("NOT_AN_EVEN"); 
   }
   println!("End of main");
}

Das obige Beispiel gibt einen Fehler zurück, wenn der der Variablen zugewiesene Wert ungerade ist.

Ausgabe

thread 'main' panicked at 'NOT_AN_EVEN', main.rs:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Ergebnisaufzählung und behebbare Fehler

Aufzählungsergebnis - <T, E> kann verwendet werden, um behebbare Fehler zu behandeln. Es hat zwei Varianten -OK und Err. T und E sind generische Typparameter. T stellt den Typ des Werts dar, der in einem Erfolgsfall innerhalb der OK-Variante zurückgegeben wird, und E stellt den Typ des Fehlers dar, der in einem Fehlerfall innerhalb der Err-Variante zurückgegeben wird.

enum Result<T,E> {
   OK(T),
   Err(E)
}

Lassen Sie uns dies anhand eines Beispiels verstehen -

use std::fs::File;
fn main() {
   let f = File::open("main.jpg"); 
   //this file does not exist
   println!("{:?}",f);
}

Das Programm gibt OK (Datei) zurück, wenn die Datei bereits vorhanden ist, und Err (Fehler), wenn die Datei nicht gefunden wird.

Err(Error { repr: Os { code: 2, message: "No such file or directory" } })

Lassen Sie uns nun sehen, wie mit der Err-Variante umgegangen wird.

Das folgende Beispiel behandelt einen Fehler, der beim Öffnen der Datei mit dem zurückgegeben wird match Erklärung

use std::fs::File;
fn main() {
   let f = File::open("main.jpg");   // main.jpg doesn't exist
   match f {
      Ok(f)=> {
         println!("file found {:?}",f);
      },
      Err(e)=> {
         println!("file not found \n{:?}",e);   //handled error
      }
   }
   println!("end of main");
}

NOTE- Das Programm druckt am Ende des Hauptereignisses obwohl Datei wurde nicht gefunden. Dies bedeutet, dass das Programm Fehler ordnungsgemäß behandelt hat.

Ausgabe

file not found
Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }
end of main

Illustration

Die Funktion is_even gibt einen Fehler zurück, wenn die Zahl keine gerade Zahl ist. Die Funktion main () behandelt diesen Fehler.

fn main(){
   let result = is_even(13);
   match result {
      Ok(d)=>{
         println!("no is even {}",d);
      },
      Err(msg)=>{
         println!("Error msg is {}",msg);
      }
   }
   println!("end of main");
}
fn is_even(no:i32)->Result<bool,String> {
   if no%2==0 {
      return Ok(true);
   } else {
      return Err("NOT_AN_EVEN".to_string());
   }
}

NOTE- Da die Hauptfunktion Fehler ordnungsgemäß behandelt, wird das Ende der Hauptanweisung gedruckt.

Ausgabe

Error msg is NOT_AN_EVEN
end of main

auspacken () und erwarten ()

Die Standardbibliothek enthält einige Hilfsmethoden, die beide Aufzählungen - Ergebnis <T, E> und Option <T> - implementieren. Sie können sie verwenden, um Fehlerfälle zu vereinfachen, in denen Sie wirklich nicht erwarten, dass etwas fehlschlägt. Bei Erfolg einer Methode wird die Funktion "Unwrap" verwendet, um das tatsächliche Ergebnis zu extrahieren.

Sr.Nr. Methode Unterschrift & Beschreibung
1 auspacken

unwrap(self): T

Erwartet, dass self Ok / Some ist, und gibt den darin enthaltenen Wert zurück. Wenn jaErr oder None Stattdessen wird eine Panik ausgelöst, wenn der Inhalt des Fehlers angezeigt wird.

2 erwarten von

expect(self, msg: &str): T

Verhält sich wie Auspacken, außer dass zusätzlich zum Inhalt des Fehlers eine benutzerdefinierte Nachricht ausgegeben wird, bevor in Panik geraten wird.

auspacken()

Die Funktion unwrap () gibt das tatsächliche Ergebnis zurück, mit dem eine Operation erfolgreich ist. Es wird eine Panik mit einer Standardfehlermeldung zurückgegeben, wenn ein Vorgang fehlschlägt. Diese Funktion ist eine Abkürzung für Match-Anweisung. Dies wird im folgenden Beispiel gezeigt -

fn main(){
   let result = is_even(10).unwrap();
   println!("result is {}",result);
   println!("end of main");
}
fn is_even(no:i32)->Result<bool,String> {
   if no%2==0 {
      return Ok(true);
   } else {
      return Err("NOT_AN_EVEN".to_string());
   }
}
result is true
end of main

Ändern Sie den obigen Code, um eine ungerade Zahl an die zu übergeben is_even() Funktion.

Die Funktion unwrap () gerät in Panik und gibt eine Standardfehlermeldung zurück, wie unten gezeigt

thread 'main' panicked at 'called `Result::unwrap()` on 
an `Err` value: "NOT_AN_EVEN"', libcore\result.rs:945:5
note: Run with `RUST_BACKTRACE=1` for a backtrace

erwarten von()

Das Programm kann im Falle einer Panik eine benutzerdefinierte Fehlermeldung zurückgeben. Dies wird im folgenden Beispiel gezeigt -

use std::fs::File;
fn main(){
   let f = File::open("pqr.txt").expect("File not able to open");
   //file does not exist
   println!("end of main");
}

Die Funktion require () ähnelt unwrap (). Der einzige Unterschied besteht darin, dass eine benutzerdefinierte Fehlermeldung mit "Expect" angezeigt werden kann.

Ausgabe

thread 'main' panicked at 'File not able to open: Error { repr: Os 
{ code: 2, message: "No such file or directory" } }', src/libcore/result.rs:860
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Generika sind eine Möglichkeit, Code für mehrere Kontexte mit unterschiedlichen Typen zu schreiben. In Rust beziehen sich Generika auf die Parametrisierung von Datentypen und Merkmalen. Mit Generics können Sie präziseren und saubereren Code schreiben, indem Sie die Codeduplizierung reduzieren und die Typensicherheit gewährleisten. Das Konzept der Generika kann auf Methoden, Funktionen, Strukturen, Aufzählungen, Sammlungen und Merkmale angewendet werden.

Das <T> syntaxDer als Typparameter bekannte Parameter wird zum Deklarieren eines generischen Konstrukts verwendet. T steht für einen beliebigen Datentyp.

Abbildung: Generische Sammlung

Im folgenden Beispiel wird ein Vektor deklariert, der nur Ganzzahlen speichern kann.

fn main(){
   let mut vector_integer: Vec<i32> = vec![20,30];
   vector_integer.push(40);
   println!("{:?}",vector_integer);
}

Ausgabe

[20, 30, 40]

Betrachten Sie das folgende Snippet -

fn main() {
   let mut vector_integer: Vec<i32> = vec![20,30];
   vector_integer.push(40);
   vector_integer.push("hello"); 
   //error[E0308]: mismatched types
   println!("{:?}",vector_integer);
}

Das obige Beispiel zeigt, dass ein Vektor vom Typ Integer nur Integer-Werte speichern kann. Wenn wir also versuchen, einen Zeichenfolgenwert in die Auflistung zu verschieben, gibt der Compiler einen Fehler zurück. Generika machen Sammlungen typsicherer.

Abbildung: Generische Struktur

Der Parameter type stellt einen Typ dar, den der Compiler später ausfüllt.

struct Data<T> {
   value:T,
}
fn main() {
   //generic type of i32
   let t:Data<i32> = Data{value:350};
   println!("value is :{} ",t.value);
   //generic type of String
   let t2:Data<String> = Data{value:"Tom".to_string()};
   println!("value is :{} ",t2.value);
}

Das obige Beispiel deklariert eine generische Struktur mit dem Namen Data . Der Typ <T> gibt einen Datentyp an. Die Funktion main () erstellt zwei Instanzen - eine Ganzzahlinstanz und eine Zeichenfolgeninstanz - der Struktur.

Ausgabe

value is :350
value is :Tom

Züge

Merkmale können verwendet werden, um einen Standardsatz von Verhaltensweisen (Methoden) über mehrere Strukturen hinweg zu implementieren. Eigenschaften sind wieinterfacesin objektorientierter Programmierung. Die Syntax des Merkmals ist wie folgt:

Deklariere ein Merkmal

trait some_trait {
   //abstract or method which is empty
   fn method1(&self);
   // this is already implemented , this is free
   fn method2(&self){
      //some contents of method2
   }
}

Merkmale können konkrete Methoden (Methoden mit Körper) oder abstrakte Methoden (Methoden ohne Körper) enthalten. Verwenden Sie eine konkrete Methode, wenn die Methodendefinition von allen Strukturen geteilt wird, die das Merkmal implementieren. Eine Struktur kann jedoch eine durch das Merkmal definierte Funktion überschreiben.

Verwenden Sie abstrakte Methoden, wenn die Methodendefinition für die implementierenden Strukturen variiert.

Syntax - Implementieren Sie ein Merkmal

impl some_trait for structure_name {
   // implement method1() there..
   fn method1(&self ){
   }
}

Die folgenden Beispiele definieren ein Merkmal Druck mit einem Verfahren print () , die durch die Struktur realisiert ist Buch .

fn main(){
   //create an instance of the structure
   let b1 = Book {
      id:1001,
      name:"Rust in Action"
   };
   b1.print();
}
//declare a structure
struct Book {
   name:&'static str,
   id:u32
}
//declare a trait
trait Printable {
   fn print(&self);
}
//implement the trait
impl Printable for Book {
   fn print(&self){
      println!("Printing book with id:{} and name {}",self.id,self.name)
   }
}

Ausgabe

Printing book with id:1001 and name Rust in Action

Allgemeine Funktionen

Das Beispiel definiert eine generische Funktion, die einen an sie übergebenen Parameter anzeigt. Der Parameter kann von einem beliebigen Typ sein. Der Typ des Parameters sollte das Merkmal Anzeige implementieren, damit sein Wert vom Druck gedruckt werden kann! Makro.

use std::fmt::Display;

fn main(){
   print_pro(10 as u8);
   print_pro(20 as u16);
   print_pro("Hello TutorialsPoint");
}

fn print_pro<T:Display>(t:T){
   println!("Inside print_pro generic function:");
   println!("{}",t);
}

Ausgabe

Inside print_pro generic function:
10
Inside print_pro generic function:
20
Inside print_pro generic function:
Hello TutorialsPoint

In diesem Kapitel wird erläutert, wie Sie Werte von der Standardeingabe (Tastatur) akzeptieren und Werte für die Standardausgabe (Konsole) anzeigen. In diesem Kapitel werden auch die Übergabe von Befehlszeilenargumenten erläutert.

Reader- und Writer-Typen

Die Standardbibliotheksfunktionen von Rust für die Ein- und Ausgabe sind nach zwei Merkmalen organisiert:

  • Read
  • Write
Sr.Nr. Eigenschaft & Beschreibung Beispiel
1

Read

Typen, die Read implementieren, verfügen über Methoden zur byteorientierten Eingabe. Sie heißen Leser

Stdin, Datei
2

Write

Typen, die Write implementieren, unterstützen sowohl die byteorientierte als auch die UTF-8-Textausgabe. Sie heißen Schriftsteller.

Stdout, Datei

Eigenschaft lesen

Readerssind Komponenten, aus denen Ihr Programm Bytes lesen kann. Beispiele sind das Lesen von Eingaben über die Tastatur, Dateien usw. Dieread_line() Die Methode dieses Merkmals kann verwendet werden, um Daten zeilenweise aus einer Datei oder einem Standardeingabestream zu lesen.

Sr.Nr. Merkmal Methode & Beschreibung
1 Lesen

read_line(&mut line)->Result

Liest eine Textzeile und hängt sie an die Zeile an, bei der es sich um eine Zeichenfolge handelt. Der Rückgabewert ist ein io :: Result, die Anzahl der gelesenen Bytes.

Illustration - Lesen von der Konsole - stdin ()

Rust-Programme müssen möglicherweise zur Laufzeit Werte vom Benutzer akzeptieren. Im folgenden Beispiel werden Werte von der Standardeingabe (Tastatur) gelesen und auf der Konsole gedruckt.

fn main(){
   let mut line = String::new();
   println!("Enter your name :");
   let b1 = std::io::stdin().read_line(&mut line).unwrap();
   println!("Hello , {}", line);
   println!("no of bytes read , {}", b1);
}

Die Funktion stdin () gibt ein Handle an den Standardeingabestream des aktuellen Prozesses zurück, auf den die Funktion read_line angewendet werden kann. Diese Funktion versucht, alle im Eingabepuffer vorhandenen Zeichen zu lesen, wenn sie auf ein Zeilenendezeichen stößt.

Ausgabe

Enter your name :
Mohtashim
Hello , Mohtashim
no of bytes read , 10

Merkmal schreiben

Writerssind Komponenten, in die Ihr Programm Bytes schreiben kann. Beispiele sind das Drucken von Werten auf die Konsole, das Schreiben in Dateien usw. Mit der write () -Methode dieses Merkmals können Daten in eine Datei oder einen Standardausgabestream geschrieben werden.

Sr.Nr. Merkmal Methode & Beschreibung
1 Schreiben

write(&buf)->Result

Schreibt einige der Bytes im Slice-Buf in den zugrunde liegenden Stream. Es gibt ein io :: Result zurück, die Anzahl der geschriebenen Bytes.

Illustration - Schreiben an die Konsole - stdout ()

Der Druck! oder drucken! Makros können verwendet werden, um Text auf der Konsole anzuzeigen. Sie können jedoch auch die Standardbibliotheksfunktion write () verwenden, um Text für die Standardausgabe anzuzeigen.

Betrachten wir ein Beispiel, um dies zu verstehen.

use std::io::Write;
fn main() {
   let b1 = std::io::stdout().write("Tutorials ".as_bytes()).unwrap();
   let b2 = std::io::stdout().write(String::from("Point").as_bytes()).unwrap();
   std::io::stdout().write(format!("\nbytes written {}",(b1+b2)).as_bytes()).unwrap();
}

Ausgabe

Tutorials Point
bytes written 15

Die Standardbibliotheksfunktion stdout () gibt ein Handle an den Standardausgabestream des aktuellen Prozesses zurück, an den diewriteFunktion kann angewendet werden. Die write () -Methode gibt die Aufzählung Result zurück. Das unwrap () ist eine Hilfsmethode, um das tatsächliche Ergebnis aus der Aufzählung zu extrahieren. Die Unwrap-Methode sendet Panik, wenn ein Fehler auftritt.

NOTE - Datei-E / A wird im nächsten Kapitel behandelt.

Kommandozeilenargumente

CommandLine-Argumente werden vor der Ausführung an ein Programm übergeben. Sie sind wie Parameter, die an Funktionen übergeben werden. Mit CommandLine-Parametern können Werte an die Funktion main () übergeben werden. Dasstd::env::args() Gibt die Befehlszeilenargumente zurück.

Illustration

Im folgenden Beispiel werden Werte als Befehlszeilenargumente an die Funktion main () übergeben. Das Programm wird unter dem Dateinamen main.rs erstellt .

//main.rs
fn main(){
   let cmd_line = std::env::args();
   println!("No of elements in arguments is :{}",cmd_line.len()); 
   //print total number of values passed
   for arg in cmd_line {
      println!("[{}]",arg); //print all values passed 
      as commandline arguments
   }
}

Das Programm generiert nach dem Kompilieren eine Datei main.exe . Mehrere Befehlszeilenparameter sollten durch Leerzeichen getrennt werden. Führen Sie main.exe vom Terminal aus als main.exe aus. Hallo tutorialspoint .

NOTE- Hallo und Tutorialspoint sind Befehlszeilenargumente.

Ausgabe

No of elements in arguments is :3
[main.exe]
[hello]
[tutorialspoint]

Die Ausgabe zeigt 3 Argumente, da die Datei main.exe das erste Argument ist.

Illustration

Das folgende Programm berechnet die Summe der als Befehlszeilenargumente übergebenen Werte. Eine durch Leerzeichen getrennte Liste ganzzahliger Werte wird an das Programm übergeben.

fn main(){
   let cmd_line = std::env::args();
   println!("No of elements in arguments is 
   :{}",cmd_line.len()); 
   // total number of elements passed

   let mut sum = 0;
   let mut has_read_first_arg = false;

   //iterate through all the arguments and calculate their sum

   for arg in cmd_line {
      if has_read_first_arg { //skip the first argument since it is the exe file name
         sum += arg.parse::<i32>().unwrap();
      }
      has_read_first_arg = true; 
      // set the flag to true to calculate sum for the subsequent arguments.
   }
   println!("sum is {}",sum);
}

Bei Ausführung des Programms als main.exe 1 2 3 4 lautet die Ausgabe -

No of elements in arguments is :5
sum is 10

Neben dem Lesen und Schreiben auf der Konsole ermöglicht Rust das Lesen und Schreiben in Dateien.

Die Dateistruktur repräsentiert eine Datei. Es ermöglicht einem Programm, Lese- / Schreibvorgänge für eine Datei auszuführen. Alle Methoden in der Dateistruktur geben eine Variante der io :: Result-Enumeration zurück.

Die häufig verwendeten Methoden der Dateistruktur sind in der folgenden Tabelle aufgeführt:

Sr.Nr. Modul Methode Unterschrift Beschreibung
1 std :: fs :: File öffnen() pub fn open <P: AsRef> (Pfad: P) -> Ergebnis Mit der Methode open static kann eine Datei im schreibgeschützten Modus geöffnet werden.
2 std :: fs :: File erstellen() pub fn create <P: AsRef> (Pfad: P) -> Ergebnis Die statische Methode öffnet eine Datei im Nur-Schreib-Modus. Wenn die Datei bereits vorhanden war, wird der alte Inhalt zerstört. Andernfalls wird eine neue Datei erstellt.
3 std :: fs :: remove_file Datei löschen() pub fn remove_file <P: AsRef> (Pfad: P) -> Ergebnis <()> Entfernt eine Datei aus dem Dateisystem. Es gibt keine Garantie dafür, dass die Datei sofort gelöscht wird.
4 std :: fs :: OpenOptions append () pub fn append (& mut self, append: bool) -> & mut OpenOptions Legt die Option für den Anhänge-Modus der Datei fest.
5 std :: io :: Writes write_all () fn write_all(&mut self, buf: &[u8]) -> Result<()> Attempts to write an entire buffer into this write.
6 std::io::Read read_to_string() fn read_to_string(&mut self, buf: &mut String) -> Result Reads all bytes until EOF in this source, appending them to buf.

Write to a File

Let us see an example to understand how to write a file.

The following program creates a file 'data.txt'. The create() method is used to create a file. The method returns a file handle if the file is created successfully. The last line write_all function will write bytes in newly created file. If any of the operations fail, the expect() function returns an error message.

use std::io::Write;
fn main() {
   let mut file = std::fs::File::create("data.txt").expect("create failed");
   file.write_all("Hello World".as_bytes()).expect("write failed");
   file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed");
   println!("data written to file" );
}

Output

data written to file

Read from a File

The following program reads the contents in a file data.txt and prints it to the console. The "open" function is used to open an existing file. An absolute or relative path to the file is passed to the open() function as a parameter. The open() function throws an exception if the file does not exist, or if it is not accessible for whatever reason. If it succeeds, a file handle to such file is assigned to the "file" variable.

The "read_to_string" function of the "file" handle is used to read contents of that file into a string variable.

use std::io::Read;

fn main(){
   let mut file = std::fs::File::open("data.txt").unwrap();
   let mut contents = String::new();
   file.read_to_string(&mut contents).unwrap();
   print!("{}", contents);
}

Output

Hello World
TutorialsPoint

Delete a file

The following example uses the remove_file() function to delete a file. The expect() function returns a custom message in case an error occurs.

use std::fs;
fn main() {
   fs::remove_file("data.txt").expect("could not remove file");
   println!("file is removed");
}

Output

file is removed

Append data to a file

The append() function writes data to the end of the file. This is shown in the example given below −

use std::fs::OpenOptions;
use std::io::Write;

fn main() {
   let mut file = OpenOptions::new().append(true).open("data.txt").expect(
      "cannot open file");
   file.write_all("Hello World".as_bytes()).expect("write failed");
   file.write_all("\nTutorialsPoint".as_bytes()).expect("write failed");
   println!("file append success");
}

Output

file append success

Copy a file

The following example copies the contents in a file to a new file.

use std::io::Read;
use std::io::Write;

fn main() {
   let mut command_line: std::env::Args = std::env::args();
   command_line.next().unwrap();
   // skip the executable file name
   // accept the source file
   let source = command_line.next().unwrap();
   // accept the destination file
   let destination = command_line.next().unwrap();
   let mut file_in = std::fs::File::open(source).unwrap();
   let mut file_out = std::fs::File::create(destination).unwrap();
   let mut buffer = [0u8; 4096];
   loop {
      let nbytes = file_in.read(&mut buffer).unwrap();
      file_out.write(&buffer[..nbytes]).unwrap();
      if nbytes < buffer.len() { break; }
   }
}

Execute the above program as main.exe data.txt datacopy.txt. Two command line arguments are passed while executing the file −

  • the path to the source file
  • the destination file

Cargo is the package manager for RUST. This acts like a tool and manages Rust projects.

Some commonly used cargo commands are listed in the table below −

Sr.No Command & Description
1

cargo build

Compiles the current project.

2

cargo check

Analyzes the current project and report errors, but don't build object files.

3

cargo run

Builds and executes src/main.rs.

4

cargo clean

Removes the target directory.

5

cargo update

Updates dependencies listed in Cargo.lock.

6

cargo new

Creates a new cargo project.

Cargo helps to download third party libraries. Therefore, it acts like a package manager. You can also build your own libraries. Cargo is installed by default when you install Rust.

To create a new cargo project, we can use the commands given below.

Create a binary crate

cargo new project_name --bin

Create a library crate

cargo new project_name --lib

To check the current version of cargo, execute the following command −

cargo --version

Illustration - Create a Binary Cargo project

The game generates a random number and prompts the user to guess the number.

Step 1 - Create a project folder

Open the terminal and type the following command cargo new guess-game-app --bin.

This will create the following folder structure.

guess-game-app/
   -->Cargo.toml
   -->src/
      main.rs

The cargo new command is used to create a crate. The --bin flag indicates that the crate being created is a binary crate. Public crates are stored in a central repository called crates.io https://crates.io/.

Step 2 - Include references to external libraries

This example needs to generate a random number. Since the internal standard library does not provide random number generation logic, we need to look at external libraries or crates. Let us use rand crate which is available at crates.io website crates.io

The https://crates.io/crates/rand is a rust library for random number generation. Rand provides utilities to generate random numbers, to convert them to useful types and distributions, and some randomness-related algorithms.

The following diagram shows crate.io website and search result for rand crate.

Copy the version of rand crate to the Cargo.toml file rand = "0.5.5".

[package]
name = "guess-game-app"
version = "0.1.0"
authors = ["Mohtashim"]

[dependencies]
rand = "0.5.5"

Step 3: Compile the Project

Navigate to the project folder. Execute the command cargo build on the terminal window −

Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.5.5
Downloading rand_core v0.2.2
Downloading winapi v0.3.6
Downloading rand_core v0.3.0
   Compiling winapi v0.3.6
   Compiling rand_core v0.3.0
   Compiling rand_core v0.2.2
   Compiling rand v0.5.5
   Compiling guess-game-app v0.1.0 
   (file:///E:/RustWorks/RustRepo/Code_Snippets/cargo-projects/guess-game-app)
   Finished dev [unoptimized + debuginfo] target(s) in 1m 07s

The rand crate and all transitive dependencies (inner dependencies of rand) will be automatically downloaded.

Step 4 - Understanding the Business Logic

Let us now see how the business logic works for the number guessing game −

  • Game initially generates a random number.

  • A user is asked to enter input and guess the number.

  • If number is less than the generated number, a message “Too low” is printed.

  • If number is greater than the generated number, a message “Too high” is printed.

  • If the user enters the number generated by the program, the game exits.

Step 5 - Edit the main.rs file

Fügen Sie die Geschäftslogik zur Datei main.rs hinzu.

use std::io;
extern crate rand; 
//importing external crate
use rand::random;
fn get_guess() -> u8 {
   loop {
      println!("Input guess") ;
      let mut guess = String::new();
      io::stdin().read_line(&mut guess)
         .expect("could not read from stdin");
      match guess.trim().parse::<u8>(){ //remember to trim input to avoid enter spaces
         Ok(v) => return v,
         Err(e) => println!("could not understand input {}",e)
      }
   }
}
fn handle_guess(guess:u8,correct:u8)-> bool {
   if guess < correct {
      println!("Too low");
      false

   } else if guess> correct {
      println!("Too high");
      false
   } else {
      println!("You go it ..");
      true
   }
}
fn main() {
   println!("Welcome to no guessing game");

   let correct:u8 = random();
   println!("correct value is {}",correct);
   loop {
      let guess = get_guess();
      if handle_guess(guess,correct){
         break;
      }
   }
}

Schritt 6 - Kompilieren Sie das Projekt und führen Sie es aus

Führen Sie den Befehl Frachtlauf auf dem Terminal aus. Stellen Sie sicher, dass das Terminal auf das Projektverzeichnis zeigt.

Welcome to no guessing game
correct value is 97
Input guess
20
Too low
Input guess
100
Too high
Input guess
97
You got it ..

In diesem Kapitel erfahren Sie, wie Iteratoren und Abschlüsse in RUST funktionieren.

Iteratoren

Ein Iterator hilft beim Durchlaufen einer Sammlung von Werten wie Arrays, Vektoren, Karten usw. Iteratoren implementieren das Iterator-Merkmal, das in der Rust-Standardbibliothek definiert ist. Die iter () -Methode gibt ein Iteratorobjekt der Auflistung zurück. Werte in einem Iteratorobjekt werden als Elemente bezeichnet. Die next () -Methode des Iterators kann verwendet werden, um die Elemente zu durchlaufen. Die next () -Methode gibt am Ende der Auflistung den Wert None zurück.

Im folgenden Beispiel wird ein Iterator verwendet, um Werte aus einem Array zu lesen.

fn main() {
   //declare an array
   let a = [10,20,30];

   let mut iter = a.iter(); 
   // fetch an iterator object for the array
   println!("{:?}",iter);

   //fetch individual values from the iterator object
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
}

Ausgabe

Iter([10, 20, 30])
Some(10)
Some(20)
Some(30)
None

Wenn eine Sammlung wie ein Array oder ein Vektor ein Iterator-Merkmal implementiert, kann sie mit der for ... in-Syntax wie unten gezeigt durchlaufen werden.

fn main() {
   let a = [10,20,30];
   let iter = a.iter();
   for data in iter{
      print!("{}\t",data);
   }
}

Ausgabe

10 20 30

Die folgenden 3 Methoden geben ein Iteratorobjekt aus einer Sammlung zurück, wobei T die Elemente in einer Sammlung darstellt.

Sr.Nr. Methoden & Beschreibung
1

iter()

gibt einen Iterator über & T (Verweis auf T)

2

into_iter()

gibt einen Iterator über T.

3

iter_mut()

gibt einen Iterator über & mut T.

Abbildung: iter ()

Die Funktion iter () verwendet das Konzept der Ausleihe. Es gibt einen Verweis auf jedes Element der Sammlung zurück, wobei die Sammlung unberührt bleibt und nach der Schleife wieder verwendet werden kann.

fn main() {
   let names = vec!["Kannan", "Mohtashim", "Kiran"];
   for name in names.iter() {
      match name {
         &"Mohtashim" => println!("There is a rustacean among us!"),
         _ => println!("Hello {}", name),
      }
   }
   println!("{:?}",names); 
   // reusing the collection after iteration
}

Ausgabe

Hello Kannan
There is a rustacean among us!
Hello Kiran
["Kannan", "Mohtashim", "Kiran"]

Illustration - into_iter ()

Diese Funktion verwendet das Konzept des Eigentums. Es verschiebt Werte in der Sammlung in ein Iter-Objekt, dh die Sammlung wird verbraucht und steht nicht mehr zur Wiederverwendung zur Verfügung.

fn main(){
   let names = vec!["Kannan", "Mohtashim", "Kiran"];
   for name in names.into_iter() {
      match name {
         "Mohtashim" => println!("There is a rustacean among us!"),
         _ => println!("Hello {}", name),
      }
   }
   // cannot reuse the collection after iteration
   //println!("{:?}",names); 
   //Error:Cannot access after ownership move
}

Ausgabe

Hello Kannan
There is a rustacean among us!
Hello Kiran

Illustration - für und iter_mut ()

Diese Funktion ähnelt der Funktion iter () . Diese Funktion kann jedoch Elemente in der Sammlung ändern.

fn main() {
   let mut names = vec!["Kannan", "Mohtashim", "Kiran"];
   for name in names.iter_mut() {
      match name {
         &mut "Mohtashim" => println!("There is a rustacean among us!"),
         _ => println!("Hello {}", name),
      }
   }
   println!("{:?}",names);
   //// reusing the collection after iteration
}

Ausgabe

Hello Kannan
There is a rustacean among us!
Hello Kiran
["Kannan", "Mohtashim", "Kiran"]

Schließung

Schließen bezieht sich auf eine Funktion innerhalb einer anderen Funktion. Dies sind anonyme Funktionen - Funktionen ohne Namen. Mit Closure kann einer Variablen eine Funktion zugewiesen werden. Dadurch kann ein Programm eine Funktion als Parameter an andere Funktionen übergeben. Das Schließen wird auch als Inline-Funktion bezeichnet. Auf Variablen in der äußeren Funktion kann über Inline-Funktionen zugegriffen werden.

Syntax: Definieren eines Abschlusses

Eine Abschlussdefinition kann optional Parameter haben. Die Parameter sind in zwei vertikalen Balken eingeschlossen.

let closure_function = |parameter| {
   //logic
}

Die Syntax, die ein Closure aufruft, wird implementiert FnZüge. Es kann also mit aufgerufen werden() Syntax.

closure_function(parameter);    //invoking

Illustration

Das folgende Beispiel definiert einen Abschluss is_even innerhalb der Funktion main () . Der Abschluss gibt true zurück, wenn eine Zahl gerade ist, und false, wenn die Zahl ungerade ist.

fn main(){
   let is_even = |x| {
      x%2==0
   };
   let no = 13;
   println!("{} is even ? {}",no,is_even(no));
}

Ausgabe

13 is even ? false

Illustration

fn main(){
   let val = 10; 
   // declared outside
   let closure2 = |x| {
      x + val //inner function accessing outer fn variable
   };
   println!("{}",closure2(2));
}

Die Funktion main () deklariert einen variablen Wert und einen Abschluss. Der Abschluss greift auf die in der äußeren Funktion main () deklarierte Variable zu .

Ausgabe

12

Rust ordnet standardmäßig alles auf dem Stapel zu. Sie können Dinge auf dem Heap speichern, indem Sie sie in intelligente Zeiger wie Box einschließen . Typen wie Vec und String helfen implizit bei der Heap-Zuordnung. Intelligente Zeiger implementieren die in der folgenden Tabelle aufgeführten Merkmale. Diese Eigenschaften der intelligenten Zeiger unterscheiden sie von einer gewöhnlichen Struktur -

Sr.Nr. Name des Merkmals Paketbeschreibung
1 Deref

std::ops::Deref

Wird für unveränderliche Dereferenzierungsvorgänge wie * v verwendet.

2 Fallen

std::ops::Drop

Wird verwendet, um Code auszuführen, wenn ein Wert den Gültigkeitsbereich verlässt. Dies wird manchmal als Destruktor bezeichnet

In diesem Kapitel lernen wir die Boxintelligenter Zeiger. Wir werden auch lernen, wie man einen benutzerdefinierten intelligenten Zeiger wie Box erstellt.

Box

Mit dem Box-Smart-Zeiger, der auch als Box bezeichnet wird, können Sie Daten auf dem Heap und nicht auf dem Stapel speichern. Der Stapel enthält den Zeiger auf die Heap-Daten. Eine Box hat keinen Leistungsaufwand, außer das Speichern ihrer Daten auf dem Heap.

Lassen Sie uns sehen, wie Sie mit einer Box einen i32-Wert auf dem Heap speichern.

fn main() {
   let var_i32 = 5; 
   //stack
   let b = Box::new(var_i32); 
   //heap
   println!("b = {}", b);
}

Ausgabe

b = 5

Verwenden Sie die Dereferenzierung, um auf einen Wert zuzugreifen, auf den eine Variable zeigt. Das * wird als Dereferenzierungsoperator verwendet. Lassen Sie uns sehen, wie die Dereferenzierung mit Box verwendet wird.

fn main() {
   let x = 5; 
   //value type variable
   let y = Box::new(x); 
   //y points to a new value 5 in the heap

   println!("{}",5==x);
   println!("{}",5==*y); 
   //dereferencing y
}

Die Variable x ist ein Werttyp mit dem Wert 5. Der Ausdruck 5 == x gibt also true zurück. Die Variable y zeigt auf den Heap. Um auf den Wert im Heap zuzugreifen, müssen wir mit * y dereferenzieren . * y gibt den Wert 5 zurück. Der Ausdruck 5 == * y gibt also true zurück.

Ausgabe

true
true

Illustration - Deref-Eigenschaft

Das Deref-Merkmal, das von der Standardbibliothek bereitgestellt wird, erfordert die Implementierung einer Methode namens deref , die sich selbst ausleiht und einen Verweis auf die inneren Daten zurückgibt. Im folgenden Beispiel wird eine Struktur MyBox erstellt , bei der es sich um einen generischen Typ handelt. Es implementiert das Merkmal Deref . Dieses Merkmal hilft uns, mit * y auf von y umschlossene Heap-Werte zuzugreifen .

use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> { 
   // Generic structure with static method new
   fn new(x:T)-> MyBox<T> {
      MyBox(x)
   }
}
impl<T> Deref for MyBox<T> {
   type Target = T;
   fn deref(&self) -> &T {
      &self.0 //returns data
   }
}
fn main() {
   let x = 5;
   let y = MyBox::new(x); 
   // calling static method
   
   println!("5==x is {}",5==x);
   println!("5==*y is {}",5==*y); 
   // dereferencing y
   println!("x==*y is {}",x==*y);
   //dereferencing y
}

Ausgabe

5==x is true
5==*y is true
x==*y is true

Illustration - Tropfeneigenschaft

Das Drop-Merkmal enthält die drop () -Methode. Diese Methode wird aufgerufen, wenn eine Struktur, die dieses Merkmal implementiert hat, den Gültigkeitsbereich verlässt. In einigen Sprachen muss der Programmierer bei jeder Verwendung einer Instanz eines intelligenten Zeigers Code aufrufen, um Speicher oder Ressourcen freizugeben. In Rust können Sie mithilfe der Drop-Eigenschaft eine automatische Freigabe des Speichers erreichen.

use std::ops::Deref;

struct MyBox<T>(T);
impl<T> MyBox<T> {
   fn new(x:T)->MyBox<T>{
      MyBox(x)
   }
}
impl<T> Deref for MyBox<T> {
   type Target = T;
      fn deref(&self) -< &T {
      &self.0
   }
}
impl<T> Drop for MyBox<T>{
   fn drop(&mut self){
      println!("dropping MyBox object from memory ");
   }
}
fn main() {
   let x = 50;
   MyBox::new(x);
   MyBox::new("Hello");
}

Im obigen Beispiel wird die Drop-Methode zweimal aufgerufen, wenn zwei Objekte im Heap erstellt werden.

dropping MyBox object from memory
dropping MyBox object from memory

Bei der gleichzeitigen Programmierung werden verschiedene Teile eines Programms unabhängig voneinander ausgeführt. Andererseits werden bei der parallelen Programmierung verschiedene Teile eines Programms gleichzeitig ausgeführt. Beide Modelle sind gleich wichtig, da mehr Computer ihre mehreren Prozessoren nutzen.

Themen

Wir können Threads verwenden, um Codes gleichzeitig auszuführen. In aktuellen Betriebssystemen wird der Code eines ausgeführten Programms in einem Prozess ausgeführt, und das Betriebssystem verwaltet mehrere Prozesse gleichzeitig. Innerhalb Ihres Programms können Sie auch unabhängige Teile haben, die gleichzeitig ausgeführt werden. Die Funktionen, mit denen diese unabhängigen Teile ausgeführt werden, werden als Threads bezeichnet.

Thread erstellen

Das thread::spawnFunktion wird verwendet, um einen neuen Thread zu erstellen. Die Spawn-Funktion wird als Parameter geschlossen. Der Abschluss definiert Code, der vom Thread ausgeführt werden soll. Im folgenden Beispiel wird Text aus einem Hauptthread und anderer Text aus einem neuen Thread gedruckt.

//import the necessary modules
use std::thread;
use std::time::Duration;

fn main() {
   //create a new thread
   thread::spawn(|| {
      for i in 1..10 {
         println!("hi number {} from the spawned thread!", i);
         thread::sleep(Duration::from_millis(1));
      }
   });
   //code executed by the main thread
   for i in 1..5 {
      println!("hi number {} from the main thread!", i);
      thread::sleep(Duration::from_millis(1));
   }
}

Ausgabe

hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the main thread!
hi number 2 from the spawned thread!
hi number 3 from the main thread!
hi number 3 from the spawned thread!
hi number 4 from the spawned thread!
hi number 4 from the main thread!

Der Haupt-Thread druckt Werte von 1 bis 4.

NOTE- Der neue Thread wird gestoppt, wenn der Haupt-Thread endet. Die Ausgabe dieses Programms kann jedes Mal etwas anders sein.

Das thread::sleepDie Funktion zwingt einen Thread, seine Ausführung für kurze Zeit zu stoppen, sodass ein anderer Thread ausgeführt werden kann. Die Threads werden sich wahrscheinlich abwechseln, aber das ist nicht garantiert - es hängt davon ab, wie das Betriebssystem die Threads plant. In diesem Lauf wird der Hauptthread zuerst gedruckt, obwohl die Druckanweisung des erzeugten Threads zuerst im Code angezeigt wird. Selbst wenn der erzeugte Thread so programmiert ist, dass er Werte bis 9 druckt, wurde er erst vor dem Herunterfahren des Hauptthreads auf 5 gesetzt.

Griffe verbinden

Ein gespawnter Thread hat möglicherweise keine Chance, vollständig oder vollständig ausgeführt zu werden. Dies liegt daran, dass der Haupt-Thread schnell abgeschlossen wird. Die Funktion spawn <F, T> (f: F) -> JoinHandlelt; T> gibt ein JoinHandle zurück. Die join () -Methode in JoinHandle wartet, bis der zugehörige Thread beendet ist.

use std::thread;
use std::time::Duration;

fn main() {
   let handle = thread::spawn(|| {
      for i in 1..10 {
         println!("hi number {} from the spawned thread!", i);
         thread::sleep(Duration::from_millis(1));
      }
   });
   for i in 1..5 {
      println!("hi number {} from the main thread!", i);
      thread::sleep(Duration::from_millis(1));
   }
   handle.join().unwrap();
}

Ausgabe

hi number 1 from the main thread!
hi number 1 from the spawned thread!
hi number 2 from the spawned thread!
hi number 2 from the main thread!
hi number 3 from the spawned thread!
hi number 3 from the main thread!
hi number 4 from the main thread!
hi number 4 from the spawned thread!
hi number 5 from the spawned thread!
hi number 6 from the spawned thread!
hi number 7 from the spawned thread!
hi number 8 from the spawned thread!
hi number 9 from the spawned thread!

Der Haupt-Thread und der erzeugte Thread wechseln weiter.

NOTE - Der Hauptthread wartet auf den Abschluss des gespawnten Threads aufgrund des Aufrufs von join() Methode.