Rust - Guida rapida

Rust è un linguaggio di programmazione a livello di sistema, sviluppato da Graydon Hoare. Mozilla Labs ha successivamente acquisito il programma.

Linguaggi di programmazione per sistemi applicativi e v / s

I linguaggi di programmazione dell'applicazione come Java / C # vengono utilizzati per creare software, che fornisce servizi all'utente direttamente. Ci aiutano a creare applicazioni aziendali come fogli di calcolo, elaboratori di testi, applicazioni web o applicazioni mobili.

I linguaggi di programmazione dei sistemi come C / C ++ vengono utilizzati per creare software e piattaforme software. Possono essere utilizzati per creare sistemi operativi, motori di gioco, compilatori, ecc. Questi linguaggi di programmazione richiedono un elevato grado di interazione hardware.

I sistemi e i linguaggi di programmazione delle applicazioni devono affrontare due problemi principali:

  • È difficile scrivere codice protetto.
  • È difficile scrivere codice multi-thread.

Perché ruggine?

Rust si concentra su tre obiettivi:

  • Safety
  • Speed
  • Concurrency

Il linguaggio è stato progettato per sviluppare software altamente affidabile e veloce in modo semplice. Rust può essere utilizzato per scrivere programmi di alto livello su programmi specifici dell'hardware.

Prestazione

Il linguaggio di programmazione Rust non ha un Garbage Collector (GC) di progettazione. Ciò migliora le prestazioni in fase di esecuzione.

Sicurezza della memoria in fase di compilazione

Il software creato utilizzando Rust è al sicuro da problemi di memoria come puntatori penzolanti, sovraccarichi del buffer e perdite di memoria.

Applicazioni multi-thread

Le regole di proprietà e sicurezza della memoria di Rust forniscono concorrenza senza gare di dati.

Supporto per Web Assembly (WASM)

Web Assembly aiuta a eseguire algoritmi ad alta intensità di calcolo nel browser, su dispositivi incorporati o altrove. Funziona alla velocità del codice nativo. Rust può essere compilato in Web Assembly per un'esecuzione veloce e affidabile.

L'installazione di Rust è resa semplice rustup, uno strumento basato su console per la gestione delle versioni di Rust e degli strumenti associati.

Installazione su Windows

Impariamo come installare RUST su Windows.

  • L'installazione di Visual Studio 2013 o superiore con strumenti C ++ è obbligatoria per eseguire il programma Rust su Windows. Innanzitutto, scarica Visual Studio da qui VS 2013 Express

  • Scarica e installa rustup strumento per Windows. rustup-init.exeè disponibile per il download qui - Rust Lang

  • Doppio click rustup-init.exefile. Facendo clic, apparirà la seguente schermata.

  • Premere Invio per l'installazione predefinita. Al termine dell'installazione, viene visualizzata la seguente schermata.

  • Dalla schermata di installazione, è chiaro che i file relativi a Rust sono archiviati nella cartella -

    C: \ Users \ {PC} \. Cargo \ bin

I contenuti della cartella sono:

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
  • Cargoè il gestore di pacchetti per Rust. Per verificare secargo è installato, eseguire il seguente comando:

C:\Users\Admin>cargo -V
cargo 1.29.0 (524a578d7 2018-08-05)
  • Il compilatore per Rust è rustc. Per verificare la versione del compilatore, eseguire il seguente comando:

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

Installazione su Linux / Mac

Installare rustup su Linux o macOS, apri un terminale e inserisci il seguente comando.

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

Il comando scarica uno script e avvia l'installazione di rustupstrumento, che installa l'ultima versione stabile di Rust. Potrebbe essere richiesta la password. Se l'installazione è andata a buon fine, apparirà la seguente riga:

Rust is installed now. Great!

Lo script di installazione aggiunge automaticamente Rust al PATH del sistema dopo il prossimo accesso. Per iniziare a usare Rust subito invece di riavviare il tuo terminale, esegui il seguente comando nella tua shell per aggiungere Rust al tuo PATH di sistema manualmente -

$ source $HOME/.cargo/env

In alternativa, puoi aggiungere la seguente riga al tuo ~ / .bash_profile -

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

NOTE - Quando si tenta di compilare un programma Rust e si ottengono errori che indicano che un linker non può essere eseguito, significa che un linker non è installato sul sistema e sarà necessario installarne uno manualmente.

Utilizzo dei tutorial Point Coding Ground per RUST

Un Read-Evaluate-Print Loop (REPL) è una shell interattiva facile da usare per compilare ed eseguire programmi per computer. Se vuoi compilare ed eseguire programmi Rust online all'interno del browser, usa Tutorialspoint Coding Ground .

Questo capitolo spiega la sintassi di base del linguaggio Rust attraverso un file HelloWorld esempio.

  • Creare un HelloWorld-App cartella e vai a quella cartella sul terminale

C:\Users\Admin>mkdir HelloWorld-App
C:\Users\Admin>cd HelloWorld-App
C:\Users\Admin\HelloWorld-App>
  • Per creare un file Rust, esegui il seguente comando:

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

I file di programma Rust hanno estensione .rs. Il comando precedente crea un file vuotoHello.rse lo apre in NOTEpad. Aggiungi il codice fornito di seguito a questo file -

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

Il programma precedente definisce una funzione main fn main () . La parola chiave fn viene utilizzata per definire una funzione. Il principale () è una funzione predefinita che funge da punto di ingresso al programma. println! è una macro predefinita in Rust. Viene utilizzato per stampare una stringa (qui Hello) sulla console. Le chiamate macro sono sempre contrassegnate da un punto esclamativo - ! .

  • Compila il file Hello.rs file utilizzando rustc.

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

Dopo la corretta compilazione del programma, viene generato un file eseguibile ( nome_file.exe ). Per verificare se il file .exe viene generato, eseguire il seguente comando.

C:\Users\Admin\HelloWorld-App>dir
//lists the files in folder
Hello.exe
Hello.pdb
Hello.rs
  • Esegui il file Hello.exe e verifica l'output.

Cos'è una macro?

Rust fornisce un potente sistema macro che consente la meta-programmazione. Come hai visto nell'esempio precedente, le macro sembrano funzioni, tranne per il fatto che il loro nome termina con un botto (!), Ma invece di generare una chiamata di funzione, le macro vengono espanse in codice sorgente che viene compilato con il resto del programma. Pertanto, forniscono più funzionalità di runtime a un programma a differenza delle funzioni. Le macro sono una versione estesa delle funzioni.

Utilizzando il println! Macro: sintassi

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

Commenti in Rust

I commenti sono un modo per migliorare la leggibilità di un programma. I commenti possono essere usati per includere informazioni aggiuntive su un programma come l'autore del codice, suggerimenti su una funzione / costrutto, ecc. Il compilatore ignora i commenti.

Rust supporta i seguenti tipi di commenti:

  • Commenti su una sola riga (//) - Qualsiasi testo compreso tra // e la fine di una riga viene considerato un commento

  • Commenti su più righe (/ * * /): questi commenti possono occupare più righe.

Esempio

//this is single line comment

/* This is a
   Multi-line comment
*/

Esegui online

I programmi Rust possono essere eseguiti online tramite Tutorialspoint Coding Ground . Scrivi il programma HelloWorld nella scheda Editor e fai clic su Esegui per visualizzare il risultato.

Il Type System rappresenta i diversi tipi di valori supportati dalla lingua. Il Type System verifica la validità dei valori forniti, prima che vengano memorizzati o manipolati dal programma. Ciò garantisce che il codice si comporti come previsto. Il Type System consente inoltre suggerimenti più ricchi sul codice e documentazione automatizzata.

Rust è un linguaggio tipizzato staticamente. Ogni valore in Rust è di un certo tipo di dati. Il compilatore può dedurre automaticamente il tipo di dati della variabile in base al valore ad essa assegnato.

Dichiara una variabile

Utilizzare il let parola chiave per dichiarare una variabile.

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);
}

Nell'esempio precedente, il tipo di dati delle variabili verrà dedotto dai valori ad esse assegnati. Ad esempio, Rust assegnerà il tipo di dati stringa alla variabile company_string , il tipo di dati float a rating_float , ecc.

Il println! macro richiede due argomenti:

  • Una sintassi speciale {} , che è il segnaposto
  • Il nome della variabile o una costante

Il segnaposto verrà sostituito dal valore della variabile

L'output dello snippet di codice sopra sarà:

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

Tipi scalari

Un tipo scalare rappresenta un singolo valore. Ad esempio, 10,3.14, "c". Rust ha quattro tipi di scalari primari.

  • Integer
  • Floating-point
  • Booleans
  • Characters

Impareremo a conoscere ogni tipo nelle nostre sezioni successive.

Numero intero

Un numero intero è un numero senza una componente frazionaria. In poche parole, il tipo di dati intero viene utilizzato per rappresentare i numeri interi.

I numeri interi possono essere ulteriormente classificati come con segno e senza segno. Gli interi con segno possono memorizzare valori sia negativi che positivi. Gli interi senza segno possono memorizzare solo valori positivi. Di seguito viene fornita una descrizione dettagliata se i tipi interi vengono forniti:

Sr.No. Taglia Firmato Non firmato
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 Arco isize usize

La dimensione di un numero intero può essere arch . Ciò significa che la dimensione del tipo di dati sarà derivata dall'architettura della macchina. Un numero intero la cui dimensione è arch sarà di 32 bit su una macchina x86 e 64 bit su una macchina x64. Un intero arch viene utilizzato principalmente durante l'indicizzazione di una sorta di raccolta.

Illustrazione

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);
}

L'output sarà come indicato di seguito:

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

Il codice precedente restituirà un errore di compilazione se si sostituisce il valore di età con un valore a virgola mobile.

Intervallo intero

Ogni variante con segno può memorizzare numeri da - (2 ^ (n-1) a 2 ^ (n-1) -1 , dove n è il numero di bit utilizzati dalla variante. Ad esempio, i8 può memorizzare numeri da - (2 ^ 7) a 2 ^ 7-1 - qui abbiamo sostituito n con 8.

Ogni variante senza segno può memorizzare numeri da 0 a (2 ^ n) -1 . Ad esempio, u8 può memorizzare numeri da 0 a 2 ^ 7 , che è uguale a 0 a 255.

Overflow di numeri interi

Un integer overflow si verifica quando il valore assegnato a una variabile intera supera l'intervallo definito da Rust per il tipo di dati. Facci capire questo con un esempio:

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);
}

L'intervallo valido della variabile u8 senza segno è compreso tra 0 e 255. Nell'esempio precedente, alle variabili vengono assegnati valori maggiori di 255 (limite superiore per una variabile intera in Rust). All'esecuzione, il codice precedente restituirà un avviso:warning − literal out of range for u8per le variabili peso, altezza e punteggio. I valori di overflow dopo 255 inizieranno da 0, 1, 2, ecc. L'output finale senza preavviso è come mostrato di seguito -

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

Galleggiante

Il tipo di dati Float in Rust può essere classificato come f32 e f64. Il tipo f32 è un float a precisione singola e f64 ha una precisione doppia. Il tipo predefinito è f64. Considera il seguente esempio per comprendere meglio il tipo di dati float.

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);
}

L'output sarà come mostrato di seguito -

interest is 8.35
cost is 15000.6

Casting automatico del tipo

Il casting automatico del tipo non è consentito in Rust. Considera il seguente frammento di codice. Alla variabile float viene assegnato un valore interointerest.

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

Il compilatore lancia un file mismatched types error come indicato di seguito.

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)

Separatore di numeri

Per una facile leggibilità di numeri grandi, possiamo utilizzare un separatore visivo _ trattino basso per separare le cifre. Cioè 50.000 può essere scritto come 50_000. Questo è mostrato nell'esempio seguente.

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);
}

L'output è fornito di seguito:

float value 11000.555001
int value 50000

Booleano

I tipi booleani hanno due possibili valori: vero o falso . Utilizzare ilbool parola chiave per dichiarare una variabile booleana.

Illustrazione

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

L'output del codice precedente sarà:

Is Rust Programming Fun ? true

Personaggio

Il tipo di dati carattere in Rust supporta numeri, alfabeti, Unicode e caratteri speciali. Utilizzare ilcharparola chiave per dichiarare una variabile del tipo di dati carattere. Il tipo char di Rust rappresenta un valore scalare Unicode, il che significa che può rappresentare molto di più del semplice ASCII. I valori scalari Unicode vanno daU+0000 per U+D7FF e U+E000 per U+10FFFF compreso.

Consideriamo un esempio per capire di più sul tipo di dati Character.

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);
}

L'output del codice precedente sarà:

special character is @
alphabet is A
emoji is

Una variabile è una memoria con nome che i programmi possono manipolare. In poche parole, una variabile aiuta i programmi a memorizzare i valori. Le variabili in Rust sono associate a un tipo di dati specifico. Il tipo di dati determina la dimensione e il layout della memoria della variabile, l'intervallo di valori che possono essere memorizzati all'interno di quella memoria e l'insieme di operazioni che possono essere eseguite sulla variabile.

Regole per la denominazione di una variabile

In questa sezione, impareremo le diverse regole per la denominazione di una variabile.

  • Il nome di una variabile può essere composto da lettere, cifre e il carattere di sottolineatura.

  • Deve iniziare con una lettera o un trattino basso.

  • Le lettere maiuscole e minuscole sono distinte perché Rust fa distinzione tra maiuscole e minuscole.

Sintassi

Il tipo di dati è facoltativo durante la dichiarazione di una variabile in Rust. Il tipo di dati viene dedotto dal valore assegnato alla variabile.

Di seguito viene fornita la sintassi per la dichiarazione di una variabile.

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

Illustrazione

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

L'output del codice precedente sarà fees is 25000 and salary is 35000.

Immutabile

Per impostazione predefinita, le variabili sono immutabili - lette solo in Rust. In altre parole, il valore della variabile non può essere modificato una volta che un valore è associato a un nome di variabile.

Cerchiamo di capirlo con un esempio.

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

L'output sarà come mostrato di seguito -

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)

Il messaggio di errore indica la causa dell'errore: non è possibile assegnare valori due volte a tariffe variabili immutabili. Questo è uno dei tanti modi in cui Rust consente ai programmatori di scrivere codice e trae vantaggio dalla sicurezza e dalla facile concorrenza.

Mutevole

Le variabili sono immutabili per impostazione predefinita. Prefisso il nome della variabile conmutparola chiave per renderlo mutevole. Il valore di una variabile modificabile può essere modificato.

La sintassi per la dichiarazione di una variabile mutabile è la seguente:

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);
}

L'output dello snippet è fornito di seguito:

fees is 25000
fees changed is 35000

Le costanti rappresentano valori che non possono essere modificati. Se dichiari una costante, non è possibile che il suo valore cambi. La parola chiave per l'utilizzo delle costanti èconst. Le costanti devono essere digitate in modo esplicito. Di seguito è riportata la sintassi per dichiarare una costante.

const VARIABLE_NAME:dataType = value;

Convenzione di denominazione costante di ruggine

La convenzione di denominazione per le costanti è simile a quella delle variabili. Tutti i caratteri in un nome costante sono solitamente in maiuscolo. A differenza della dichiarazione delle variabili, illet la parola chiave non viene utilizzata per dichiarare una costante.

Abbiamo usato le costanti in Rust nell'esempio seguente:

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
}

Costanti v / s variabili

In questa sezione, impareremo i fattori di differenziazione tra costanti e variabili.

  • Le costanti vengono dichiarate utilizzando il const mentre le variabili vengono dichiarate utilizzando la let parola chiave.

  • Una dichiarazione di variabile può opzionalmente avere un tipo di dati mentre una dichiarazione di costante deve specificare il tipo di dati. Ciò significa che const USER_LIMIT = 100 genererà un errore.

  • Una variabile dichiarata utilizzando il letla parola chiave è per impostazione predefinita immutabile. Tuttavia, hai la possibilità di modificarlo utilizzando ilmutparola chiave. Le costanti sono immutabili.

  • Le costanti possono essere impostate solo su un'espressione costante e non sul risultato di una chiamata di funzione o su qualsiasi altro valore che verrà calcolato in fase di esecuzione.

  • Le costanti possono essere dichiarate in qualsiasi ambito, incluso l'ambito globale, il che le rende utili per valori di cui molte parti del codice devono conoscere.

Shadowing di variabili e costanti

Rust consente ai programmatori di dichiarare variabili con lo stesso nome. In tal caso, la nuova variabile sostituisce la variabile precedente.

Cerchiamo di capirlo con un esempio.

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

Il codice sopra dichiara due variabili dal nome stipendio. Alla prima dichiarazione viene assegnato un valore di 100.00 mentre alla seconda dichiarazione viene assegnato il valore 1.50. La seconda variabile ombreggia o nasconde la prima variabile durante la visualizzazione dell'output.

Produzione

The value of salary is :1.50

Rust supporta variabili con diversi tipi di dati durante lo shadowing.

Considera il seguente esempio.

Il codice dichiara due variabili con il nome uname. Alla prima dichiarazione viene assegnato un valore stringa, mentre alla seconda dichiarazione viene assegnato un numero intero. La funzione len restituisce il numero totale di caratteri in un valore stringa.

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

Produzione

name changed to integer: 9

A differenza delle variabili, le costanti non possono essere ombreggiate. Se le variabili nel programma precedente vengono sostituite con costanti, il compilatore genererà un errore.

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

Il tipo di dati String in Rust può essere classificato come segue:

  • String Literal(&str)

  • Oggetto stringa(String)

String Literal

I valori letterali stringa (& str) vengono utilizzati quando il valore di una stringa è noto in fase di compilazione. Le stringhe letterali sono un insieme di caratteri, che sono codificati in una variabile. Ad esempio, let company = "Tutorials Point" . I valori letterali stringa si trovano nel modulo std :: str. Le stringhe letterali sono anche note come sezioni di stringa.

L'esempio seguente dichiara due stringhe letterali: azienda e posizione .

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

I valori letterali stringa sono statici per impostazione predefinita. Ciò significa che i letterali stringa sono garantiti per essere validi per la durata dell'intero programma. Possiamo anche specificare esplicitamente la variabile come statica come mostrato di seguito -

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

Il programma precedente genererà il seguente output:

company is : TutorialsPoint location :Hyderabad

Oggetto stringa

Il tipo di oggetto String viene fornito nella libreria standard. A differenza del valore letterale stringa, il tipo di oggetto stringa non fa parte del linguaggio principale. È definito come struttura pubblica nella libreria standard pub struct String . String è una collezione coltivabile. È di tipo mutabile e codificato UTF-8. IlStringil tipo di oggetto può essere utilizzato per rappresentare i valori di stringa forniti in fase di esecuzione. L'oggetto stringa viene allocato nell'heap.

Sintassi

Per creare un oggetto String, possiamo usare una delle seguenti sintassi:

String::new()

La sintassi precedente crea una stringa vuota

String::from()

Questo crea una stringa con un valore predefinito passato come parametro al file from() metodo.

L'esempio seguente illustra l'uso di un oggetto String.

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());
}

L'esempio precedente crea due stringhe: un oggetto stringa vuoto utilizzando il nuovo metodo e un oggetto stringa dal valore letterale stringa utilizzando il metodo from .

L'output è come mostrato di seguito:

length is 0
length is 14

Metodi comuni - Oggetto stringa

Sr.No. Metodo Firma Descrizione
1 nuovo() pub const fn new () → String Crea una nuova stringa vuota.
2 accordare() fn to_string (& self) → String Converte il valore specificato in una stringa.
3 sostituire() pub fn sostituisce <'a, P> (&' a self, from: P, to: & str) → String Sostituisce tutte le corrispondenze di un pattern con un'altra stringa.
4 as_str () pub fn as_str (& self) → & str Estrae una sezione di stringa contenente l'intera stringa.
5 Spingere() pub fn push (& mut self, ch: char) Aggiunge il carattere specificato alla fine di questa stringa.
6 push_str () pub fn push_str (& mut self, string: & str) Aggiunge una determinata porzione di stringa alla fine di questa stringa.
7 len () pub fn len (& self) → usize Restituisce la lunghezza di questa stringa, in byte.
8 trim () pub fn trim (& self) → & str Restituisce una sezione di stringa con spazi bianchi iniziali e finali rimossi.
9 split_whitespace () pub fn split_whitespace (& self) → SplitWhitespace Divide una sezione di stringa per spazi e restituisce un iteratore.
10 Diviso() pub fn split <'a, P> (&' a self, pat: P) → Split <'a, P>, dove P è pattern può essere & str, char o una chiusura che determina la divisione. Restituisce un iteratore sulle sottostringhe di questa sezione di stringa, separate da caratteri corrispondenti a un modello.
11 caratteri () pub fn chars (& self) → Chars Restituisce un iteratore sui caratteri di una sezione di stringa.

Illustrazione: new ()

Un oggetto stringa vuoto viene creato utilizzando il new()e il suo valore è impostato su hello .

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

Produzione

Il programma di cui sopra genera il seguente output:

hello

Illustrazione: to_string ()

Per accedere a tutti i metodi dell'oggetto String, converti un valore letterale stringa in un tipo di oggetto utilizzando l'estensione to_string() funzione.

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

Produzione

Il programma di cui sopra genera il seguente output:

Hello TutorialsPoint , Hello!

Illustrazione: sostituire ()

Il replace()funzione accetta due parametri: il primo parametro è uno schema di stringhe da cercare e il secondo parametro è il nuovo valore da sostituire. Nell'esempio precedente, Hello appare due volte nella stringa name1 .

La funzione di sostituzione sostituisce tutte le occorrenze della stringa Hello con Howdy.

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

Produzione

Il programma di cui sopra genera il seguente output:

Howdy TutorialsPoint , Howdy!

Illustrazione: as_str ()

Il as_str() la funzione estrae una sezione di stringa contenente l'intera stringa.

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);
}

Produzione

Il programma di cui sopra genera il seguente output:

displaying string literal example_string

Illustrazione: push ()

Il push() la funzione aggiunge il carattere specificato alla fine di questa stringa.

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

Produzione

Il programma di cui sopra genera il seguente output:

Tutorials

Illustrazione: push_str ()

Il push_str() la funzione aggiunge una determinata porzione di stringa alla fine di una stringa.

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

Produzione

Il programma di cui sopra genera il seguente output:

Tutorials Point

Illustrazione: len ()

Il len() funzione restituisce il numero totale di caratteri in una stringa (inclusi gli spazi).

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

Produzione

Il programma di cui sopra genera il seguente output:

length is 20

Illustrazione: trim ()

La funzione trim () rimuove gli spazi iniziali e finali in una stringa. NOTA che questa funzione non rimuoverà gli spazi in linea.

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());
}

Produzione

Il programma di cui sopra genera il seguente output:

Before trim
length is 24

After trim
length is 15

Illustrazione: split_whitespace ()

Il split_whitespace()divide la stringa di input in stringhe diverse. Restituisce un iteratore, quindi stiamo iterando i token come mostrato di seguito -

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;
   }
}

Produzione

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

Illustrazione: stringa split ()

Il split() stringrestituisce un iteratore su sottostringhe di una sezione di stringa, separate da caratteri corrispondenti a un modello. La limitazione del metodo split () è che il risultato non può essere memorizzato per un uso successivo. Ilcollect può essere utilizzato per memorizzare il risultato restituito da split () come vettore.

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]);
}

L'esempio sopra divide la stringa fullname, ogni volta che incontra una virgola (,).

Produzione

token is Kannan
token is Sudhakaran
token is Tutorialspoint

firstName is Kannan
lastname is Sudhakaran
company is Tutorialspoint

Illustrazione: chars ()

È possibile accedere ai singoli caratteri in una stringa utilizzando il metodo chars. Consideriamo un esempio per capirlo.

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

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

Produzione

T
u
t
o
r
i
a
l
s

Concatenazione di stringhe con l'operatore +

Un valore stringa può essere aggiunto a un'altra stringa. Questa è chiamata concatenazione o interpolazione. Il risultato della concatenazione di stringhe è un nuovo oggetto stringa. L'operatore + utilizza internamente un metodo add . La sintassi della funzione add accetta due parametri. Il primo parametro è self - l'oggetto stringa stesso e il secondo parametro è un riferimento del secondo oggetto stringa. Questo è mostrato di seguito -

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

Illustrazione: concatenazione di stringhe

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

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

L'output sarà come indicato di seguito

TutorialsPoint

Illustrazione: tipo Casting

L'esempio seguente illustra la conversione di un numero in un oggetto stringa:

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");
}

L'output sarà come indicato di seguito

2020
true

Illustrazione: formato! Macro

Un altro modo per aggiungere insieme oggetti String è usare una funzione macro chiamata format. L'uso di Format! è come mostrato di seguito.

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

L'output sarà come indicato di seguito

Tutorials Point

Un operatore definisce alcune funzioni che verranno eseguite sui dati. I dati su cui lavorano gli operatori sono chiamati operandi. Considera la seguente espressione:

7 + 5 = 12

Qui, i valori 7, 5 e 12 sono operandi, mentre + e = sono operatori.

I principali operatori di Rust possono essere classificati come:

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

Operatori aritmetici

Supponiamo che i valori nelle variabili aeb siano rispettivamente 10 e 5.

Mostra esempi

Suor n Operatore Descrizione Esempio
1 + (Aggiunta) restituisce la somma degli operandi a + b è 15
2 -(Sottrazione) restituisce la differenza dei valori ab è 5
3 * (Moltiplicazione) restituisce il prodotto dei valori a * b è 50
4 / (Divisione) esegue l'operazione di divisione e restituisce il quoziente a / b è 2
5 % (Modulo) esegue l'operazione di divisione e restituisce il resto a% b è 0

NOTE - Gli operatori ++ e - non sono supportati in Rust.

Operatori relazionali

Gli operatori relazionali testano o definiscono il tipo di relazione tra due entità. Gli operatori relazionali vengono utilizzati per confrontare due o più valori. Gli operatori relazionali restituiscono un valore booleano, vero o falso.

Supponiamo che il valore di A sia 10 e B sia 20.

Mostra esempi

Suor n Operatore Descrizione Esempio
1 > Più grande di (A> B) è False
2 < Minore di (A <B) è vero
3 > = Maggiore o uguale a (A> = B) è False
4 <= Minore o uguale a (A <= B) è vero
5 == Uguaglianza (A == B) è fals
6 ! = Non uguale (A! = B) è vero

Operatori logici

Gli operatori logici vengono utilizzati per combinare due o più condizioni. Anche gli operatori logici restituiscono un valore booleano. Supponiamo che il valore della variabile A sia 10 e B sia 20.

Mostra esempi

Suor n Operatore Descrizione Esempio
1 && (E) L'operatore restituisce true solo se tutte le espressioni specificate restituiscono true (A> 10 && B> 10) è False
2 || (OR) L'operatore restituisce true se almeno una delle espressioni specificate restituisce true (A> 10 || B> 10) è vero
3 ! (NON) L'operatore restituisce l'inverso del risultato dell'espressione. Ad esempio:! (> 5) restituisce false ! (A> 10) è vero

Operatori bit per bit

Supponiamo che la variabile A = 2 e B = 3.

Mostra esempi

Suor n Operatore Descrizione Esempio
1 & (AND bit per bit) Esegue un'operazione booleana AND su ogni bit dei suoi argomenti interi. (A e B) è 2
2 | (BitWise OR) Esegue un'operazione booleana OR su ogni bit dei suoi argomenti interi. (A | B) è 3
3 ^ (XOR bit per bit) Esegue un'operazione booleana OR esclusivo su ogni bit dei suoi argomenti interi. OR esclusivo significa che l'operando uno è vero o l'operando due è vero, ma non entrambi. (A ^ B) è 1
4 ! (Bitwise non) È un operatore unario e opera invertendo tutti i bit dell'operando. (! B) è -4
5 << (Maiusc sinistro) Sposta tutti i bit nel suo primo operando a sinistra del numero di posizioni specificato nel secondo operando. I nuovi bit vengono riempiti con zeri. Spostare un valore a sinistra di una posizione equivale a moltiplicarlo per 2, spostare due posizioni equivale a moltiplicare per 4 e così via. (A << 1) è 4
6 >> (Shift destro) Operatore binario di spostamento a destra. Il valore dell'operando sinistro viene spostato a destra del numero di bit specificato dall'operando destro. (A >> 1) è 1
7 >>> (Spostamento a destra con zero) Questo operatore è proprio come l'operatore >>, tranne per il fatto che i bit spostati a sinistra sono sempre zero. (A >>> 1) è 1

Le strutture decisionali richiedono che il programmatore specifichi una o più condizioni che devono essere valutate o testate dal programma, insieme a una o più istruzioni da eseguire se la condizione è determinata essere vera e, facoltativamente, altre istruzioni da eseguire se condizione è determinata essere falsa.

Di seguito è mostrata la forma generale di una tipica struttura decisionale presente nella maggior parte dei linguaggi di programmazione:

Suor n Dichiarazione e descrizione
1

if statement

Un'istruzione if è costituita da un'espressione booleana seguita da una o più istruzioni.

2

if...else statement

Un'istruzione if può essere seguita da un'istruzione else opzionale , che viene eseguita quando l'espressione booleana è falsa.

3

else...if and nested ifstatement

È possibile utilizzare un'istruzione if o else if all'interno di un'altra istruzione if o else if .

4

match statement

Un'istruzione match consente di verificare una variabile rispetto a un elenco di valori.

Istruzione If

Il costrutto if ... else valuta una condizione prima che venga eseguito un blocco di codice.

Sintassi

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

Se l'espressione booleana restituisce true, verrà eseguito il blocco di codice all'interno dell'istruzione if. Se l'espressione booleana restituisce false, verrà eseguita la prima serie di codice dopo la fine dell'istruzione if (dopo la parentesi graffa di chiusura).

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

L'esempio sopra verrà stampato number is positive come la condizione specificata dal blocco if è vera.

if else dichiarazione

Un if può essere seguito da un optional elsebloccare. Il blocco else verrà eseguito se l'espressione booleana testata dall'istruzione if restituisce false.

Sintassi

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

Diagramma di flusso

Il ifil blocco protegge l'espressione condizionale. Il blocco associato all'istruzione if viene eseguito se l'espressione booleana restituisce true.

Il blocco if può essere seguito da un'istruzione else opzionale. Il blocco di istruzioni associato al blocco else viene eseguito se l'espressione restituisce false.

Illustrazione - Semplice se… altro

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

L'esempio sopra mostra se il valore in una variabile è pari o dispari. Il blocco if controlla la divisibilità del valore per 2 per determinare lo stesso. Ecco l'output del codice sopra -

Even

Annidato If

Il else…ifladder è utile per testare più condizioni. La sintassi è come mostrato di seguito:

Sintassi

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
}

Quando si usano le istruzioni if ​​... else ... if e else, ci sono alcuni punti da tenere a mente.

  • Un if può avere zero o un altro e deve venire dopo qualsiasi altro..if.
  • Un if può avere zero a molti altri..if e devono venire prima dell'altro.
  • Una volta che un altro .. se ha successo, nessuno dei rimanenti altrimenti .. se o altrimenti verrà testato.

Esempio: altro ... se scala

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) ;
   }
}

Lo snippet mostra se il valore è positivo, negativo o zero.

Produzione

2 is positive

Istruzione Match

L'istruzione match controlla se un valore corrente corrisponde da un elenco di valori, questo è molto simile all'istruzione switch nel linguaggio C. In primo luogo, nota che l'espressione che segue la parola chiave match non deve essere racchiusa tra parentesi.

La sintassi è come mostrato di seguito.

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

Nell'esempio riportato di seguito, state_code corrisponde a un elenco di valori MH, KL, KA, GA- se viene trovata una corrispondenza, un valore stringa viene restituito allo stato della variabile . Se non viene trovata alcuna corrispondenza, il caso predefinito _ corrisponde e viene restituito il valore Unkown .

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);
}

Produzione

Found match for MH
State name is Maharashtra

Potrebbero esserci casi in cui un blocco di codice deve essere eseguito ripetutamente. In generale, le istruzioni di programmazione vengono eseguite in sequenza: la prima istruzione in una funzione viene eseguita per prima, seguita dalla seconda e così via.

I linguaggi di programmazione forniscono varie strutture di controllo che consentono percorsi di esecuzione più complicati.

Un'istruzione loop ci consente di eseguire un'istruzione o un gruppo di istruzioni più volte. Di seguito è riportata la forma generale di un'istruzione loop nella maggior parte dei linguaggi di programmazione.

Rust fornisce diversi tipi di loop per gestire i requisiti di loop -

  • while
  • loop
  • for

Definite Loop

Un ciclo il cui numero di iterazioni è definito / fisso è definito ciclo definito. Ilfor loop è un'implementazione di un ciclo definito.

Per Loop

Il ciclo for esegue il blocco di codice per un numero di volte specificato. Può essere utilizzato per iterare su un insieme fisso di valori, come un array. La sintassi del ciclo for è la seguente

Sintassi

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

Un esempio di un ciclo for è come mostrato di seguito

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

NOTE: che la variabile x è accessibile solo all'interno del blocco for.

Produzione

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

Ciclo indefinito

Un ciclo indefinito viene utilizzato quando il numero di iterazioni in un ciclo è indeterminato o sconosciuto.

I cicli indefiniti possono essere implementati usando -

Suor n Nome e descrizione
1

While

Il ciclo while esegue le istruzioni ogni volta che la condizione specificata restituisce true

2

Loop

Il ciclo è un ciclo indefinito while (vero)

Illustrazione - per un po '

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);
}

L'output è come mostrato di seguito:

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

Illustrazione −loop

fn main(){
   //while true

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

      if x==15 {
         break;
      }
   }
}

Il breakviene utilizzata per estrarre il controllo da un costrutto. L'uso dell'interruzione in un ciclo fa sì che il programma esca dal ciclo.

Produzione

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

Continua dichiarazione

L'istruzione continue salta le istruzioni successive nell'iterazione corrente e riporta il controllo all'inizio del ciclo. A differenza dell'istruzione break, continue non esce dal ciclo. Termina l'iterazione corrente e avvia l'iterazione successiva.

Di seguito viene fornito un esempio dell'istruzione continue.

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
}

L'esempio sopra mostra il numero di valori pari compresi tra 0 e 20. Il ciclo esce dall'iterazione corrente se il numero è pari. Ciò si ottiene utilizzando l'istruzione continue.

Il conteggio dei valori dispari tra 0 e 20 è 10

Le funzioni sono gli elementi costitutivi del codice leggibile, gestibile e riutilizzabile. Una funzione è un insieme di istruzioni per eseguire un'attività specifica. Le funzioni organizzano il programma in blocchi logici di codice. Una volta definite, le funzioni possono essere chiamate per accedere al codice. Ciò rende il codice riutilizzabile. Inoltre, le funzioni facilitano la lettura e la manutenzione del codice del programma.

Una dichiarazione di funzione indica al compilatore il nome, il tipo restituito e i parametri di una funzione. Una definizione di funzione fornisce il corpo effettivo della funzione.

Suor n Descrizione della funzione
1

Defining a function

La definizione della funzione TA specifica cosa e come verrà eseguita un'attività specifica.

2

Calling or invoking a Function

Una funzione deve essere chiamata in modo da eseguirla.

3

Returning Functions

Le funzioni possono anche restituire valore insieme al controllo, al chiamante.

4

Parameterized Function

I parametri sono un meccanismo per passare valori alle funzioni.

Definizione di una funzione

Una definizione di funzione specifica cosa e come verrà eseguita un'attività specifica. Prima di utilizzare una funzione, è necessario definirla. Il corpo della funzione contiene codice che dovrebbe essere eseguito dalla funzione. Le regole per denominare una funzione sono simili a quelle di una variabile. Le funzioni vengono definite utilizzandofnparola chiave. Di seguito viene fornita la sintassi per la definizione di una funzione standard

Sintassi

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

Una dichiarazione di funzione può facoltativamente contenere parametri / argomenti. I parametri vengono utilizzati per passare valori alle funzioni.

Esempio: definizione di funzione semplice

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

Invocare una funzione

Una funzione deve essere chiamata in modo da eseguirla. Questo processo è definito comefunction invocation. I valori per i parametri devono essere passati quando viene richiamata una funzione. La funzione che richiama un'altra funzione è chiamatacaller function.

Sintassi

function_name(val1,val2,valN)

Esempio: invocare una funzione

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

Qui, main () è la funzione chiamante.

Illustrazione

L'esempio seguente definisce una funzione fn_hello(). La funzione stampa un messaggio alla console. Ilmain()funzione invoca la funzione fn_hello () .

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

Produzione

hello from function fn_hello

Restituzione di valore da una funzione

Le funzioni possono anche restituire un valore insieme al controllo, al chiamante. Tali funzioni sono chiamate funzioni di ritorno.

Sintassi

È possibile utilizzare una delle seguenti sintassi per definire una funzione con tipo restituito.

Con dichiarazione di ritorno

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

Sintassi abbreviata senza dichiarazione di ritorno

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

illustrazione

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

Produzione

pi value is 3.142857142857143

Funzione con parametri

I parametri sono un meccanismo per passare valori alle funzioni. I parametri fanno parte della firma della funzione. I valori dei parametri vengono passati alla funzione durante la sua chiamata. Se non diversamente specificato, il numero di valori passati a una funzione deve corrispondere al numero di parametri definiti.

I parametri possono essere passati a una funzione utilizzando una delle seguenti tecniche:

Passa per valore

Quando viene richiamato un metodo, viene creata una nuova posizione di archiviazione per ogni parametro del valore. I valori dei parametri effettivi vengono copiati in essi. Quindi, le modifiche apportate al parametro all'interno del metodo invocato non hanno effetto sull'argomento.

L'esempio seguente dichiara una variabile no, che inizialmente è 5. La variabile viene passata come parametro (per valore) al mutate_no_to_zero()functionnction, che cambia il valore a zero. Dopo la chiamata alla funzione, quando il controllo ritorna al metodo principale, il valore sarà lo stesso.

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);
}

Produzione

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

Passa per riferimento

Quando si passano i parametri per riferimento, a differenza dei parametri valore, non viene creata una nuova posizione di archiviazione per questi parametri. I parametri di riferimento rappresentano la stessa posizione di memoria dei parametri effettivi forniti al metodo. I valori dei parametri possono essere passati per riferimento anteponendo al nome della variabile un prefisso& .

Nell'esempio riportato di seguito, abbiamo una variabile no , che inizialmente è 5. Un riferimento alla variabile no viene passato amutate_no_to_zero()funzione. La funzione opera sulla variabile originale. Dopo la chiamata alla funzione, quando il controllo torna al metodo principale, il valore della variabile originale sarà zero.

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
}

L'operatore * viene utilizzato per accedere al valore memorizzato nella posizione di memoria che la variabile param_nopunta a. Questo è anche noto come dereferenziazione.

L'output sarà:

The value of no is 0.

Passaggio di una stringa a una funzione

La funzione main () passa un oggetto stringa alla funzione 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);
}

Produzione

param_name value is :TutorialsPoint

Tuple è un tipo di dati composto. Un tipo scalare può memorizzare solo un tipo di dati. Ad esempio, una variabile i32 può memorizzare solo un singolo valore intero. Nei tipi composti, possiamo memorizzare più di un valore alla volta e può essere di diversi tipi.

Le tuple hanno una lunghezza fissa - una volta dichiarate non possono crescere o ridursi di dimensioni. L'indice della tupla inizia da0.

Sintassi

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

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

Illustrazione

L'esempio seguente visualizza i valori in una tupla.

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

La sintassi println! ("{}", Tuple) non può essere utilizzata per visualizzare i valori in una tupla. Questo perché una tupla è un tipo composto. Usa la sintassi println! ("{:?}", tuple_name) per stampare i valori in una tupla.

Produzione

(-325, 4.9, 22)

Illustrazione

L'esempio seguente stampa singoli valori in una tupla.

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);
}

Produzione

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

Illustrazione

L'esempio seguente passa una tupla come parametro a una funzione. Le tuple vengono passate per valore alle funzioni.

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);
}

Produzione

Inside print method
(110, true, 10.9)

Distruggere

La distruzione dell'assegnazione è una caratteristica di rust in cui scompattiamo i valori di una tupla. Ciò si ottiene assegnando una tupla a variabili distinte.

Considera il seguente esempio:

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);
}

La variabile x è una tupla assegnata all'istruzione let. Ogni variabile - age, is_male e cgpa conterrà i valori corrispondenti in una tupla.

Produzione

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

In questo capitolo impareremo a conoscere un array e le varie caratteristiche ad esso associate. Prima di conoscere gli array, vediamo in che modo un array è diverso da una variabile.

Le variabili hanno le seguenti limitazioni:

  • Le variabili sono di natura scalare. In altre parole, una dichiarazione di variabile può contenere solo un singolo valore alla volta. Ciò significa che per memorizzare n valori in un programma sarà necessaria una dichiarazione di n variabili. Pertanto, l'uso di variabili non è fattibile quando è necessario memorizzare una raccolta di valori più ampia.

  • Alle variabili in un programma viene allocata la memoria in ordine casuale, rendendo così difficile recuperare / leggere i valori nell'ordine della loro dichiarazione.

Un array è una raccolta omogenea di valori. In poche parole, un array è una raccolta di valori dello stesso tipo di dati.

Caratteristiche di un array

Le caratteristiche di un array sono elencate di seguito:

  • Una dichiarazione di matrice alloca blocchi di memoria sequenziali.

  • Gli array sono statici. Ciò significa che un array una volta inizializzato non può essere ridimensionato.

  • Ogni blocco di memoria rappresenta un elemento dell'array.

  • Gli elementi della matrice sono identificati da un numero intero univoco chiamato pedice / indice dell'elemento.

  • Il popolamento degli elementi dell'array è noto come inizializzazione dell'array.

  • I valori degli elementi della matrice possono essere aggiornati o modificati ma non possono essere eliminati.

Dichiarazione e inizializzazione di array

Usa la sintassi fornita di seguito per dichiarare e inizializzare un array in Rust.

Sintassi

//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];

Nella prima sintassi, il tipo dell'array viene dedotto dal tipo di dati del primo elemento dell'array durante l'inizializzazione.

Illustrazione: Simple Array

L'esempio seguente specifica in modo esplicito la dimensione e il tipo di dati della matrice. La sintassi {:?} Della funzione println! () Viene utilizzata per stampare tutti i valori nell'array. La funzione len () viene utilizzata per calcolare la dimensione dell'array.

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

Produzione

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

Illustrazione: array senza tipo di dati

Il seguente programma dichiara un array di 4 elementi. Il tipo di dati non viene specificato esplicitamente durante la dichiarazione della variabile. In questo caso, l'array sarà di tipo integer. La funzione len () viene utilizzata per calcolare la dimensione dell'array.

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

Produzione

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

Illustrazione: valori predefiniti

L'esempio seguente crea un array e inizializza tutti i suoi elementi con un valore predefinito di -1 .

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

Produzione

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

Illustrazione: array con ciclo for

L'esempio seguente esegue un'iterazione su un array e stampa gli indici e i valori corrispondenti. Il ciclo recupera i valori dall'indice 0 a 4 (indice dell'ultimo elemento dell'array).

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]);
   }
}

Produzione

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

Illustrazione: utilizzo della funzione iter ()

La funzione iter () recupera i valori di tutti gli elementi in un array.

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);
   }
}

Produzione

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

Illustrazione: array mutevole

La parola chiave mut può essere utilizzata per dichiarare un array modificabile. L'esempio seguente dichiara una matrice modificabile e modifica il valore del secondo elemento della matrice.

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

Produzione

[10, 0, 30, 40]

Passaggio di matrici come parametri alle funzioni

Un array può essere passato per valore o per riferimento a funzioni.

Illustrazione: Passa per valore

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);
}

Produzione

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

Illustrazione: passaggio per riferimento

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);
}

Produzione

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

Dichiarazione di matrice e costanti

Consideriamo un esempio fornito di seguito per comprendere la dichiarazione e le costanti dell'array.

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

Il compilatore genererà un'eccezione. Questo perché la lunghezza di un array deve essere nota in fase di compilazione. Qui, il valore della variabile "N" verrà determinato in fase di esecuzione. In altre parole, le variabili non possono essere utilizzate per definire la dimensione di un array.

Tuttavia, il seguente programma è valido:

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

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

Il valore di un identificatore con prefisso con la parola chiave const viene definito in fase di compilazione e non può essere modificato in fase di esecuzione. usize ha le dimensioni di un puntatore, quindi la sua dimensione effettiva dipende dall'architettura per cui stai compilando il programma.

La memoria per un programma può essere allocata come segue:

  • Stack
  • Heap

Pila

Una pila segue l'ultima in ordine di prima uscita. Stack memorizza i valori dei dati per i quali la dimensione è nota in fase di compilazione. Ad esempio, una variabile di dimensione fissa i32 è una candidata per l'allocazione dello stack. La sua dimensione è nota in fase di compilazione. Tutti i tipi scalari possono essere memorizzati nello stack poiché la dimensione è fissa.

Considera un esempio di stringa, a cui viene assegnato un valore in fase di esecuzione. La dimensione esatta di una stringa di questo tipo non può essere determinata in fase di compilazione. Quindi non è un candidato per l'allocazione dello stack ma per l'allocazione dell'heap.

Mucchio

La memoria heap memorizza i valori dei dati la cui dimensione è sconosciuta al momento della compilazione. Viene utilizzato per memorizzare dati dinamici. In poche parole, una memoria heap viene allocata ai valori dei dati che possono cambiare durante il ciclo di vita del programma. L'heap è un'area della memoria meno organizzata rispetto allo stack.

Cos'è la proprietà?

Ogni valore in Rust ha una variabile che viene chiamata ownerdel valore. Ogni dato archiviato in Rust avrà un proprietario ad esso associato. Ad esempio, nella sintassi - let age = 30, age è il proprietario del valore 30 .

  • Ogni dato può avere un solo proprietario alla volta.

  • Due variabili non possono puntare alla stessa posizione di memoria. Le variabili punteranno sempre a diverse posizioni di memoria.

Trasferimento della proprietà

La proprietà del valore può essere trasferita da:

  • Assegnazione del valore di una variabile a un'altra variabile.

  • Passaggio di valore a una funzione.

  • Restituzione di valore da una funzione.

Assegnazione del valore di una variabile a un'altra variabile

Il punto chiave di vendita di Rust come linguaggio è la sua sicurezza nella memoria. La sicurezza della memoria si ottiene controllando strettamente chi può usare cosa e quando le restrizioni.

Considera il seguente frammento:

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);
}

L'esempio precedente dichiara un vettore v. L'idea di proprietà è che solo una variabile si lega a una risorsa v si lega alla risorsa o v2si lega alla risorsa. L'esempio precedente genera un errore: l' uso del valore spostato: `v` . Questo perché la proprietà della risorsa viene trasferita alla v2. Significa che la proprietà viene spostata da v2 (v2 = v) e v viene invalidata dopo lo spostamento.

Passaggio di valore a una funzione

La proprietà di un valore cambia anche quando passiamo un oggetto nell'heap a una chiusura o funzione.

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);
}

Restituzione di valore da una funzione

La proprietà passata alla funzione verrà invalidata al termine dell'esecuzione della funzione. Una soluzione è lasciare che la funzione restituisca l'oggetto di proprietà al chiamante.

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);
}

Proprietà e tipi primitivi

In caso di tipi primitivi, il contenuto di una variabile viene copiato in un'altra. Quindi, non è in corso alcun trasferimento di proprietà. Questo perché una variabile primitiva richiede meno risorse di un oggetto. Considera il seguente esempio:

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

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

L'output sarà - 10.

È molto scomodo passare la proprietà di una variabile a un'altra funzione e quindi restituire la proprietà. Rust supporta un concetto, il prestito, in cui la proprietà di un valore viene trasferita temporaneamente a un'entità e quindi restituita all'entità proprietaria originale.

Considera quanto segue:

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);
}

La funzione principale invoca una funzione print_vector () . Un vettore viene passato come parametro a questa funzione. La proprietà del vettore viene anche passata alla funzione print_vector () da main () . Il codice precedente genererà un errore come mostrato di seguito quando la funzione main () tenta di accedere al vettore v .

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

Questo perché una variabile o un valore non può più essere utilizzato dalla funzione che lo possedeva originariamente una volta che la proprietà è stata trasferita a un'altra funzione.

Cos'è il prestito?

Quando una funzione trasferisce temporaneamente il suo controllo su una variabile / valore a un'altra funzione, per un po ', viene chiamata prestito. Ciò si ottiene passando un riferimento alla variabile(& var_name)piuttosto che passare la variabile / valore stesso alla funzione. La proprietà della variabile / valore viene trasferita al proprietario originale della variabile dopo che la funzione a cui è stato passato il controllo completa l'esecuzione.

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);
}

Produzione

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

Riferimenti mutevoli

Una funzione può modificare una risorsa presa in prestito utilizzando un riferimento mutabile a tale risorsa. Un riferimento modificabile è preceduto da&mut. I riferimenti mutabili possono operare solo su variabili mutabili.

Illustrazione: mutamento di un riferimento intero

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

La funzione main () dichiara una variabile intera mutabile i e passa un riferimento mutabile di i aadd_one(). Add_one () incrementa il valore della variabile i di uno.

Illustrazione: modifica di un riferimento di stringa

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
}

La funzione main () passa un riferimento mutabile del nome della variabile alla funzione display () . La funzione di visualizzazione aggiunge una stringa aggiuntiva alla variabile del nome originale .

Produzione

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

Una fetta è un puntatore a un blocco di memoria. Le sezioni possono essere utilizzate per accedere a porzioni di dati archiviate in blocchi di memoria contigui. Può essere utilizzato con strutture dati come array, vettori e stringhe. Le sezioni utilizzano numeri di indice per accedere a porzioni di dati. La dimensione di una sezione viene determinata in fase di esecuzione.

Le sezioni sono puntatori ai dati effettivi. Vengono passati in riferimento alle funzioni, note anche come prestito.

Ad esempio, le sezioni possono essere utilizzate per recuperare una parte di un valore di stringa. Una stringa con sezioni è un puntatore all'oggetto stringa effettivo. Pertanto, dobbiamo specificare l'indice iniziale e finale di una stringa. L'indice inizia da 0 proprio come gli array.

Sintassi

let sliced_value = &data_structure[start_index..end_index]

Il valore di indice minimo è 0 e il valore di indice massimo è la dimensione della struttura dei dati. NOTA che end_index non sarà incluso nella stringa finale.

Il diagramma seguente mostra una stringa di esempio Tutorial , che ha 9 caratteri. L'indice del primo carattere è 0 e quello dell'ultimo carattere è 8.

Il codice seguente recupera 5 caratteri dalla stringa (a partire dall'indice 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);
}

Produzione

length of string is 9
rials

Illustrazione - Affettare una matrice intera

La funzione main () dichiara un array con 5 elementi. Invoca iluse_slice()funzione e le passa una porzione di tre elementi (punta all'array di dati). Le fette vengono passate per riferimento. La funzione use_slice () stampa il valore della fetta e la sua lunghezza.

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);
}

Produzione

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

Fette mobili

Il &mut la parola chiave può essere utilizzata per contrassegnare una sezione come modificabile.

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
}

Produzione

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

Il codice precedente passa uno slice modificabile alla funzione use_slice () . La funzione modifica il secondo elemento dell'array originale.

Le matrici vengono utilizzate per rappresentare una raccolta omogenea di valori. Allo stesso modo, una struttura è un altro tipo di dati definito dall'utente disponibile in Rust che ci permette di combinare elementi di dati di diversi tipi, inclusa un'altra struttura. Una struttura definisce i dati come una coppia chiave-valore.

Sintassi: dichiarazione di una struttura

La parola chiave struct viene utilizzata per dichiarare una struttura. Poiché le strutture sono tipizzate staticamente, ogni campo nella struttura deve essere associato a un tipo di dati. Le regole e le convenzioni di denominazione per una struttura sono come quelle di una variabile. Il blocco struttura deve terminare con un punto e virgola.

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

Sintassi: inizializzazione di una struttura

Dopo aver dichiarato una struttura, a ogni campo dovrebbe essere assegnato un valore. Questo è noto come inizializzazione.

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);
}

L'esempio precedente dichiara una struttura Employee con tre campi: nome, azienda ed età dei tipi. Il main () inizializza la struttura. Usa il println! macro per stampare i valori dei campi definiti nella struttura.

Produzione

Name is :Mohtashim company is TutorialsPoint age is 50

Modifica di un'istanza di struct

Per modificare un'istanza, la variabile di istanza deve essere contrassegnata come mutabile. L'esempio seguente dichiara e inizializza una struttura denominata Employee e successivamente modifica il valore del campo age a 40 da 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);

Produzione

Name is :Mohtashim company is TutorialsPoint age is 40

Passaggio di una struttura a una funzione

L'esempio seguente mostra come passare l'istanza di struct come parametro. Il metodo di visualizzazione accetta un'istanza Employee come parametro e stampa i dettagli.

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

Ecco il programma completo -

//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);
}

Produzione

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

Restituzione di struct da una funzione

Consideriamo una funzione who_is_elder () , che confronta l'età di due dipendenti e restituisce quella maggiore.

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

Ecco il programma completo -

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
}

Produzione

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

Metodo nella struttura

I metodi sono come le funzioni. Sono un gruppo logico di istruzioni di programmazione. I metodi vengono dichiarati confnparola chiave. L'ambito di un metodo è all'interno del blocco di struttura.

I metodi vengono dichiarati all'esterno del blocco della struttura. Ilimplparola chiave viene utilizzata per definire un metodo nel contesto di una struttura. Il primo parametro di un metodo sarà sempreself, che rappresenta l'istanza chiamante della struttura. I metodi operano sui membri dati di una struttura.

Per invocare un metodo, dobbiamo prima istanziare la struttura. Il metodo può essere chiamato utilizzando l'istanza della struttura.

Sintassi

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

Illustrazione

L'esempio seguente definisce una struttura Rettangolo con campi: larghezza e altezza . Procedimento zona è definito all'interno del contesto della struttura. Il metodo area accede ai campi della struttura tramite la parola chiave self e calcola l'area di un rettangolo.

//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());
}

Produzione

width is 10 height is 20 area of Rectangle is 200

Metodo statico nella struttura

I metodi statici possono essere utilizzati come metodi di utilità. Questi metodi esistono anche prima che venga creata un'istanza della struttura. I metodi statici vengono richiamati utilizzando il nome della struttura e sono accessibili senza un'istanza. A differenza dei metodi normali, un metodo statico non accetta il parametro & self .

Sintassi: dichiarazione di un metodo statico

Un metodo statico come funzioni e altri metodi possono facoltativamente contenere parametri.

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

Sintassi: invocare un metodo statico

Il nome_struttura :: sintassi viene utilizzato per accedere a un metodo statico.

structure_name::method_name(v1,v2)

Illustrazione

L'esempio seguente utilizza il metodo getInstance come una classe factory che crea e restituisce istanze della struttura Point .

//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();
}

Produzione

x =10 y=20

Nella programmazione Rust, quando dobbiamo selezionare un valore da un elenco di possibili varianti, usiamo tipi di dati di enumerazione. Un tipo enumerato viene dichiarato utilizzando la parola chiave enum . La seguente è la sintassi di enum -

enum enum_name {
   variant1,
   variant2,
   variant3
}

Illustrazione: utilizzo di un'enumerazione

L'esempio dichiara un'enumerazione - GenderCategory , che ha varianti come Male e Female. La stampa! la macro mostra il valore dell'enumerazione. Il compilatore genererà un errore il tratto std :: fmt :: Debug non è implementato per GenderCategory . L'attributo # [derive (Debug)] viene utilizzato per eliminare questo errore.

// 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);
}

Produzione

Male
Female

Struct ed Enum

L'esempio seguente definisce una struttura Person. Il campo gender è del tipo GenderCategory (che è un'enumerazione ) e può essere assegnato come valore Male o Female .

// 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);
}

L'esempio crea oggetti p1 e p2 di tipo Persona e inizializza gli attributi, il nome e il sesso per ciascuno di questi oggetti.

Produzione

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

Opzione Enum

Option è un'enumerazione predefinita nella libreria standard di Rust. Questa enumerazione ha due valori: Some (data) e None.

Sintassi

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

Qui, il tipo T rappresenta il valore di qualsiasi tipo.

Rust non supporta la parola chiave null . Il valore None , in enumOption , può essere utilizzato da una funzione per restituire un valore null. Se sono presenti dati da restituire, la funzione può restituire Some (data) .

Facci capire questo con un esempio:

Il programma definisce una funzione is_even () , con un tipo di ritorno Option. La funzione verifica se il valore passato è un numero pari. Se l'input è pari, viene restituito un valore true, altrimenti la funzione restituisce None .

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
   }
}

Produzione

None
Some(true)

Dichiarazione di corrispondenza ed enumerazione

L' istruzione match può essere utilizzata per confrontare i valori archiviati in un enum. L'esempio seguente definisce una funzione, print_size , che accetta CarType enum come parametro. La funzione confronta i valori dei parametri con un insieme predefinito di costanti e visualizza il messaggio appropriato.

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);
}

Produzione

Large sized Sports Utility car
Small sized car
medium sized car

Abbina con Opzione

L'esempio della funzione is_even , che restituisce il tipo di opzione, può anche essere implementato con l'istruzione match come mostrato di seguito -

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
   }
}

Produzione

not even

Abbina ed enum con il tipo di dati

È possibile aggiungere un tipo di dati a ciascuna variante di un'enumerazione. Nell'esempio seguente, le varianti Name e Usr_ID dell'enumerazione sono rispettivamente di tipo String e integer. L'esempio seguente mostra l'uso dell'istruzione match con un'enumerazione con un tipo di dati.

// 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);
      }
   }
}

Produzione

Name("Mohtashim")
Usr_ID(100)
Mohtashim

Un gruppo logico di codice è chiamato Modulo. Più moduli vengono compilati in un'unità chiamatacrate. I programmi Rust possono contenere una cassa binaria o una cassa libreria. Una cassa binaria è un progetto eseguibile che ha un metodo main () . Una cassa di libreria è un gruppo di componenti che possono essere riutilizzati in altri progetti. A differenza di una cassa binaria, una cassa di libreria non ha un punto di ingresso (metodo main ()). Lo strumento Cargo viene utilizzato per gestire le casse in Rust. Ad esempio, il modulo di rete contiene funzioni relative alla rete e il modulo grafico contiene funzioni relative al disegno. I moduli sono simili agli spazi dei nomi in altri linguaggi di programmazione. Le casse di terze parti possono essere scaricate utilizzando cargo da crates.io .

Suor n Termine e descrizione
1

crate

È un'unità di compilazione in Rust; Crate è compilato in binario o libreria.

2

cargo

Lo strumento ufficiale di gestione dei pacchetti Rust per le casse.

3

module

Raggruppa logicamente il codice all'interno di una cassa.

4

crates.io

Il registro ufficiale dei pacchetti Rust.

Sintassi

//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() {
   }
}

I moduli possono essere pubblici o privati. I componenti in un modulo privato non sono accessibili da altri moduli. I moduli in Rust sono privati ​​per impostazione predefinita. Al contrario, le funzioni in un modulo pubblico sono accessibili da altri moduli. I moduli dovrebbero essere preceduti dapubparola chiave per renderla pubblica. Anche le funzioni all'interno di un modulo pubblico devono essere rese pubbliche.

Illustrazione: definizione di un modulo

L'esempio definisce un modulo pubblico: i film . Il modulo contiene una funzione play () che accetta un parametro e ne stampa il valore.

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

Produzione

Playing movie Herold and Kumar

Usa parola chiave

La parola chiave use aiuta a importare un modulo pubblico.

Sintassi

use public_module_name::function_name;

Illustrazione

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

Produzione

Playing movie Herold and Kumar

Moduli annidati

I moduli possono anche essere annidati. Il modulo comedy è annidato all'interno del modulo inglese , che è ulteriormente annidato nel modulo movies . L'esempio riportato di seguito definisce una funzione riprodotta all'interno del modulo movies / english / comedy .

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());
}

Produzione

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

Illustrazione - Crea una libreria Crate e consuma in una cassa binaria

Creiamo una cassa libreria denominata movie_lib, che contiene un modulo movies. Per costruire il filemovie_lib cassa della libreria, useremo lo strumento cargo.

Passaggio 1: creare la cartella del progetto

Crea una cartella movie-app seguita da una sottocartella movie-lib . Dopo aver creato la cartella e la sottocartella, creare un filesrccartella e un file Cargo.toml in questa directory. Il codice sorgente dovrebbe andare nella cartella src . Crea i file lib.rs e movies.rs nella cartella src. Il file Cargo.toml conterrà i metadati del progetto come il numero di versione, il nome dell'autore, ecc.

La struttura della directory del progetto sarà come mostrato di seguito:

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

Passaggio 2: modifica il file Cargo.toml per aggiungere i metadati del progetto

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

Passaggio 3: modifica il file lib.rs.

Aggiungere la seguente definizione di modulo a questo file.

pub mod movies;

La riga sopra crea un modulo pubblico - movies.

Passaggio 4: modifica il file movies.rs

Questo file definirà tutte le funzioni per il modulo filmati.

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

Il codice precedente definisce una funzione play() che accetta un parametro e lo stampa sulla console.

Passaggio 5: costruire la cassa della libreria

Crea app usando il cargo buildcomando per verificare se la cassa della libreria è strutturata correttamente. Assicurati di essere alla radice del progetto: la cartella dell'app del film. Il seguente messaggio verrà visualizzato nel terminale se la compilazione riesce.

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

Passaggio 6: creare un'applicazione di prova

Crea un'altra cartella movie-lib-testnella cartella dell'app del film seguito da un file Cargo.toml e dalla cartella src. Questo progetto dovrebbe avere un metodo principale in quanto si tratta di una cassa binaria, che consumerà la cassa della libreria creata in precedenza. Crea un file main.rs nella cartella src. La struttura delle cartelle sarà quella mostrata.

movie-app
   movie-lib 
   // already completed

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

Passaggio 7: aggiungere quanto segue nel file Cargo.toml

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

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

NOTE- Il percorso della cartella della libreria è impostato come dipendenze. Il diagramma seguente mostra il contenuto di entrambi i progetti.

Passaggio 8: aggiungere quanto segue al file main.rs

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

Il codice precedente importa un pacchetto esterno chiamato movies_lib. Controlla il file Cargo.toml del progetto corrente per verificare il nome della cassa.

Passaggio 9: utilizzo della costruzione e della corsa del carico

Useremo la build del carico e la corsa del carico per costruire il progetto binario ed eseguirlo come mostrato di seguito -

La libreria di raccolta standard di Rust fornisce implementazioni efficienti delle strutture dati di programmazione generiche più comuni. Questo capitolo discute l'implementazione delle raccolte di uso comune: Vector, HashMap e HashSet.

Vettore

Un vettore è un array ridimensionabile. Memorizza i valori in blocchi di memoria contigui. La struttura predefinita Vec può essere utilizzata per creare vettori. Alcune caratteristiche importanti di un vettore sono:

  • Un vettore può crescere o ridursi in fase di esecuzione.

  • Un vettore è una raccolta omogenea.

  • Un vettore memorizza i dati come sequenza di elementi in un ordine particolare. A ogni elemento di un vettore viene assegnato un numero di indice univoco. L'indice inizia da 0 e sale a n-1 dove, n è la dimensione della raccolta. Ad esempio, in una raccolta di 5 elementi, il primo elemento sarà all'indice 0 e l'ultimo elemento all'indice 4.

  • Un vettore aggiungerà valori solo alla (o vicino) alla fine. In altre parole, un vettore può essere utilizzato per implementare uno stack.

  • La memoria per un vettore viene allocata nell'heap.

Sintassi: creazione di un vettore

let mut instance_name = Vec::new();

Il metodo statico new () della struttura Vec viene utilizzato per creare un'istanza vettoriale.

In alternativa, è anche possibile creare un vettore utilizzando il vec! macro. La sintassi è la seguente:

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

La tabella seguente elenca alcune funzioni di uso comune della struttura Vec.

Suor n Metodo Firma e descrizione
1 nuovo()

pub fn new()->Vect

Costruisce un nuovo Vec vuoto. Il vettore non verrà allocato finché gli elementi non verranno inseriti su di esso.

2 Spingere()

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

Aggiunge un elemento al retro di una raccolta.

3 rimuovere()

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

Rimuove e restituisce l'elemento all'indice di posizione all'interno del vettore, spostando tutti gli elementi dopo di esso a sinistra.

4 contiene ()

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

Restituisce vero se la fetta contiene un elemento con il valore dato.

5 len ()

pub fn len(&self) -> usize

Restituisce il numero di elementi nel vettore, denominato anche "lunghezza".

Illustrazione: creazione di un vettore - nuovo ()

Per creare un vettore, usiamo il metodo statico 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);
}

L'esempio precedente crea un vettore utilizzando il metodo statico new () definito nella struttura Vec . La funzione push (val) aggiunge il valore passato come parametro alla raccolta. La funzione len () restituisce la lunghezza del vettore.

Produzione

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

Illustrazione: creazione di un vettore - vec! Macro

Il codice seguente crea un vettore usando il vec! macro. Il tipo di dati del vettore viene dedotto dal primo valore che gli viene assegnato.

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

Produzione

[1, 2, 3]

Come accennato in precedenza, un vettore può contenere solo valori dello stesso tipo di dati. Il seguente frammento genererà un errore [E0308]: errore di tipi non corrispondenti .

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

Illustrazione: push ()

Aggiunge un elemento alla fine di una raccolta.

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

Produzione

[20, 30, 40]

Illustrazione: remove ()

Rimuove e restituisce l'elemento all'indice di posizione all'interno del vettore, spostando tutti gli elementi dopo di esso a sinistra.

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

Produzione

[10, 30]

Illustrazione - contiene ()

Restituisce vero se la fetta contiene un elemento con il valore dato -

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

Produzione

found 10
[10, 20, 30]

Illustrazione: len ()

Restituisce il numero di elementi nel vettore, denominato anche "lunghezza".

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

Produzione

size of vector is :3

Accesso ai valori da un vettore

È possibile accedere ai singoli elementi in un vettore utilizzando i numeri di indice corrispondenti. Il seguente esempio crea un annuncio vettoriale stampa il valore del primo elemento.

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

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

I valori in un vettore possono anche essere recuperati utilizzando il riferimento alla raccolta.

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);
}

Produzione

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

HashMap

Una mappa è una raccolta di coppie chiave-valore (chiamate voci). Due voci in una mappa non possono avere la stessa chiave. In breve, una mappa è una tabella di ricerca. Una HashMap memorizza le chiavi e i valori in una tabella hash. Le voci vengono memorizzate in un ordine arbitrario. La chiave viene utilizzata per cercare i valori nella HashMap. La struttura HashMap è definita nel filestd::collectionsmodulo. Questo modulo dovrebbe essere importato esplicitamente per accedere alla struttura HashMap.

Sintassi: creazione di una mappa hash

let mut instance_name = HashMap::new();

Il metodo statico new () della struttura HashMap viene utilizzato per creare un oggetto HashMap. Questo metodo crea una HashMap vuota.

Le funzioni comunemente utilizzate di HashMap sono discusse di seguito:

Suor n Metodo Firma e descrizione
1 inserire()

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

Inserisce una coppia chiave / valore, se nessuna chiave viene restituita Nessuna. Dopo l'aggiornamento, viene restituito il vecchio valore.

2 len ()

pub fn len(&self) -> usize

Restituisce il numero di elementi nella mappa.

3 ottenere()

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

Restituisce un riferimento al valore corrispondente alla chiave.

4 iter ()

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

Un iteratore che visita tutte le coppie chiave-valore in ordine arbitrario. Il tipo di elemento dell'iteratore è (& 'a K, &' a V).

5 contiene_chiave

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

Restituisce vero se la mappa contiene un valore per la chiave specificata.

6 rimuovere()

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

Rimuove una chiave dalla mappa, restituendo la chiave e il valore memorizzati se la chiave era precedentemente nella mappa.

Illustrazione: insert ()

Inserisce una coppia chiave / valore in HashMap.

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

Il programma precedente crea una HashMap e la inizializza con 2 coppie chiave-valore.

Produzione

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

Illustrazione: len ()

Restituisce il numero di elementi nella mappa

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());
}

L'esempio precedente crea una HashMap e stampa il numero totale di elementi in essa contenuti.

Produzione

size of map is 2

Illustrazione - get ()

Restituisce un riferimento al valore corrispondente alla chiave. L'esempio seguente recupera il valore per la chiave KL in HashMap.

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");
      }
   }
}

Produzione

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

Illustrazione - iter ()

Restituisce un iteratore contenente il riferimento a tutte le coppie chiave-valore in un ordine arbitrario.

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);
   }
}

Produzione

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

Illustrazione: contains_key ()

Restituisce vero se la mappa contiene un valore per la chiave specificata.

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");
   }
}

Produzione

found key

Illustrazione: remove ()

Rimuove una chiave dalla mappa.

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());
}

Produzione

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

HashSet

HashSet è un insieme di valori univoci di tipo T. L'aggiunta e la rimozione di valori è veloce ed è veloce chiedere se un dato valore è nell'insieme o meno. La struttura HashSet è definita nel modulo std :: collections. Questo modulo dovrebbe essere importato esplicitamente per accedere alla struttura HashSet.

Sintassi: creazione di un HashSet

let mut hash_set_name = HashSet::new();

Il metodo statico, nuovo , della struttura HashSet viene utilizzato per creare un HashSet. Questo metodo crea un HashSet vuoto.

La tabella seguente elenca alcuni dei metodi comunemente usati della struttura HashSet.

Suor n Metodo Firma e descrizione
1 inserire()

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

Aggiunge un valore al set. Se l'insieme non aveva questo valore presente, viene restituito true altrimenti false.

2 len ()

pub fn len(&self) -> usize

Restituisce il numero di elementi nel set.

3 ottenere()

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

Restituisce un riferimento al valore nell'insieme, se presente, uguale al valore dato.

4 iter ()

pub fn iter(&self) -> Iter

Restituisce un iteratore che visita tutti gli elementi in ordine arbitrario. Il tipo di elemento dell'iteratore è & 'a T.

5 contiene_chiave

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

Restituisce vero se il set contiene un valore.

6 rimuovere()

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

Rimuove un valore dall'insieme. Restituisce vero se il valore era presente nell'insieme.

Illustrazione - insert ()

Aggiunge un valore al set. Un HashSet non aggiunge valori duplicati alla raccolta.

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);
}

Produzione

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

Illustrazione: len ()

Restituisce il numero di elementi nel set.

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());
}

Produzione

size of the set is 3

Illustrazione - iter ()

Riesegue un iteratore visitando tutti gli elementi in ordine arbitrario.

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);
   }
}

Produzione

TutorialsPoint
Mohtashim
Kannan

Illustrazione: get ()

Restituisce un riferimento al valore nel set, se presente, che è uguale al valore dato.

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);
}

Produzione

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

Illustrazione - contiene ()

Restituisce vero se il set contiene un valore.

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");
   }  
}

Produzione

found name

Illustrazione: remove ()

Rimuove un valore dall'insieme.

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());
}

Produzione

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

In Rust, gli errori possono essere classificati in due categorie principali, come mostrato nella tabella seguente.

Suor n Nome e descrizione Utilizzo
1

Recoverable

Errori che possono essere gestiti

Enumerazione dei risultati
2

UnRecoverable

Errori che non possono essere gestiti

macro di panico

Un errore recuperabile è un errore che può essere corretto. Un programma può ritentare l'operazione non riuscita o specificare un'azione alternativa quando rileva un errore recuperabile. Gli errori ripristinabili non provocano il malfunzionamento improvviso di un programma. Un esempio di errore recuperabile è l'errore File non trovato .

Errori irreversibili causano il fallimento improvviso di un programma. Un programma non può tornare al suo stato normale se si verifica un errore irreversibile. Non può ritentare l'operazione non riuscita o annullare l'errore. Un esempio di errore irreversibile è il tentativo di accedere a una posizione oltre la fine di un array.

A differenza di altri linguaggi di programmazione, Rust non ha eccezioni. Restituisce un'enumerazione Risultato <T, E> per gli errori recuperabili, mentre chiama il filepanicmacro se il programma rileva un errore irreversibile. La macro panico provoca la chiusura improvvisa del programma.

Macro di panico ed errori irreversibili

panico! consente a un programma di terminare immediatamente e fornire un feedback al chiamante del programma. Dovrebbe essere utilizzato quando un programma raggiunge uno stato irreversibile.

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

Nell'esempio sopra, il programma terminerà immediatamente quando incontra il panico! macro.

Produzione

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

Illustrazione: panico! macro

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

L'output è come mostrato di seguito:

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.

Un programma può invocare il panico! macro se le regole aziendali vengono violate come mostrato nell'esempio seguente:

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");
}

L'esempio precedente restituisce un errore se il valore assegnato alla variabile è dispari.

Produzione

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

Enumerazione dei risultati ed errori ripristinabili

Enum Result - <T, E> può essere utilizzato per gestire gli errori recuperabili. Ha due varianti:OK e Err. T e E sono parametri di tipo generico. T rappresenta il tipo di valore che verrà restituito in un caso di successo all'interno della variante OK e E rappresenta il tipo di errore che verrà restituito in un caso di errore all'interno della variante Err.

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

Facci capire questo con l'aiuto di un esempio:

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

Il programma restituisce OK (File) se il file esiste già e Err (Errore) se il file non viene trovato.

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

Vediamo ora come gestire la variante Err.

L'esempio seguente gestisce un errore restituito durante l'apertura del file utilizzando l'estensione match dichiarazione

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- Il programma stampa fine della principale manifestazione anche se il file non è stato trovato. Ciò significa che il programma ha gestito correttamente l'errore.

Produzione

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

Illustrazione

La funzione is_even restituisce un errore se il numero non è un numero pari. La funzione main () gestisce questo errore.

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- Dal momento che le principali maniglie funzione di errore con grazia, la fine del principale dichiarazione è stampata.

Produzione

Error msg is NOT_AN_EVEN
end of main

scartare () e aspettare ()

La libreria standard contiene un paio di metodi di supporto che entrambi enumerano: Result <T, E> e Option <T> implementano. Puoi usarli per semplificare i casi di errore in cui davvero non ti aspetti che le cose falliscano. In caso di successo da un metodo, la funzione "scartare" viene utilizzata per estrarre il risultato effettivo.

Suor n Metodo Firma e descrizione
1 scartare

unwrap(self): T

Si aspetta che self sia Ok / Some e restituisce il valore contenuto all'interno. Se èErr o None invece, solleva il panico con il contenuto dell'errore visualizzato.

2 aspettarsi

expect(self, msg: &str): T

Si comporta come scartare, tranne per il fatto che emette un messaggio personalizzato prima di andare nel panico oltre al contenuto dell'errore.

scartare()

La funzione Unrap () restituisce il risultato effettivo di un'operazione riuscita. Restituisce un panico con un messaggio di errore predefinito se un'operazione non riesce. Questa funzione è un'abbreviazione per l'istruzione match. Questo è mostrato nell'esempio sotto:

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

Modificare il codice sopra per passare un numero dispari al file is_even() funzione.

La funzione Unrap () andrà in panico e restituirà un messaggio di errore predefinito come mostrato di seguito

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

aspettarsi()

Il programma può restituire un messaggio di errore personalizzato in caso di panico. Questo è mostrato nel seguente esempio:

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");
}

La funzione expected () è simile a unfrap (). L'unica differenza è che un messaggio di errore personalizzato può essere visualizzato utilizzando Prevedi.

Produzione

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.

I generici sono una struttura per scrivere codice per più contesti con tipi diversi. In Rust, i generici si riferiscono alla parametrizzazione dei tipi di dati e delle caratteristiche. Generics consente di scrivere codice più conciso e pulito riducendo la duplicazione del codice e fornendo l'indipendenza dai tipi. Il concetto di Generics può essere applicato a metodi, funzioni, strutture, enumerazioni, raccolte e tratti.

Il <T> syntaxnoto come parametro di tipo, viene utilizzato per dichiarare un costrutto generico. T rappresenta qualsiasi tipo di dati.

Illustrazione: raccolta generica

L'esempio seguente dichiara un vettore che può memorizzare solo numeri interi.

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

Produzione

[20, 30, 40]

Considera il seguente frammento:

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);
}

L'esempio sopra mostra che un vettore di tipo intero può memorizzare solo valori interi. Quindi, se proviamo a inserire un valore di stringa nella raccolta, il compilatore restituirà un errore. I generici rendono le raccolte più sicure per i tipi.

Illustrazione: struttura generica

Il parametro type rappresenta un tipo, che il compilatore compilerà in seguito.

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);
}

L'esempio precedente dichiara una struttura generica denominata Data . Il tipo <T> indica un tipo di dati. La funzione main () crea due istanze: un'istanza intera e un'istanza stringa della struttura.

Produzione

value is :350
value is :Tom

Tratti

I tratti possono essere utilizzati per implementare un insieme standard di comportamenti (metodi) su più strutture. I tratti sono comeinterfacesnella programmazione orientata agli oggetti. La sintassi del tratto è come mostrato di seguito:

Dichiara un tratto

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
   }
}

I tratti possono contenere metodi concreti (metodi con corpo) o metodi astratti (metodi senza corpo). Utilizzare un metodo concreto se la definizione del metodo sarà condivisa da tutte le strutture che implementano il tratto. Tuttavia, una struttura può scegliere di sovrascrivere una funzione definita dal tratto.

Utilizzare metodi astratti se la definizione del metodo varia per le strutture di implementazione.

Sintassi: implementa un tratto

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

I seguenti esempi definiscono un tratto stampabile con un metodo print () , che è implementato dal libro di struttura .

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)
   }
}

Produzione

Printing book with id:1001 and name Rust in Action

Funzioni generiche

L'esempio definisce una funzione generica che visualizza un parametro ad essa passato. Il parametro può essere di qualsiasi tipo. Il tipo del parametro dovrebbe implementare il tratto Display in modo che il suo valore possa essere stampato da println! macro.

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);
}

Produzione

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

Questo capitolo discute come accettare i valori dallo standard input (tastiera) e visualizzare i valori nello standard output (console). In questo capitolo, discuteremo anche del passaggio di argomenti della riga di comando.

Tipi di lettori e scrittori

Le funzionalità della libreria standard di Rust per input e output sono organizzate attorno a due tratti:

  • Read
  • Write
Suor n Tratto e descrizione Esempio
1

Read

I tipi che implementano la lettura hanno metodi per l'input orientato ai byte. Si chiamano lettori

Stdin, File
2

Write

I tipi che implementano la scrittura supportano l'output di testo sia orientato ai byte che UTF-8. Si chiamano scrittori.

Stdout, File

Leggi tratto

Readerssono componenti da cui il programma può leggere byte. Gli esempi includono la lettura di input dalla tastiera, file, eccread_line() Il metodo di questo tratto può essere utilizzato per leggere i dati, una riga alla volta, da un file o da un flusso di input standard.

Suor n Tratto Metodo e descrizione
1 Leggere

read_line(&mut line)->Result

Legge una riga di testo e la aggiunge alla riga, che è una stringa. Il valore restituito è un io :: Result, il numero di byte letti.

Illustrazione - Lettura dalla console - stdin ()

I programmi Rust potrebbero dover accettare valori dall'utente in fase di esecuzione. L'esempio seguente legge i valori dall'input standard (tastiera) e lo stampa nella console.

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);
}

La funzione stdin () restituisce un handle al flusso di input standard del processo corrente, a cui può essere applicata la funzione read_line . Questa funzione cerca di leggere tutti i caratteri presenti nel buffer di input quando incontra un carattere di fine riga.

Produzione

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

Scrivi tratto

Writerssono componenti in cui il tuo programma può scrivere byte. Gli esempi includono la stampa di valori sulla console, la scrittura su file, ecc. Il metodo write () di questa caratteristica può essere utilizzato per scrivere dati su un file o flusso di output standard.

Suor n Tratto Metodo e descrizione
1 Scrivi

write(&buf)->Result

Scrive alcuni dei byte nella slice buf nel flusso sottostante. Restituisce un io :: Result, il numero di byte scritti.

Illustrazione - Scrittura sulla console - stdout ()

La stampa! o println! le macro possono essere utilizzate per visualizzare il testo sulla console. Tuttavia, è anche possibile utilizzare la funzione della libreria standard write () per visualizzare del testo nell'output standard.

Consideriamo un esempio per capirlo.

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();
}

Produzione

Tutorials Point
bytes written 15

La funzione di libreria standard stdout () restituisce un handle al flusso di output standard del processo corrente, a cui il filewritepuò essere applicata. Il metodo write () restituisce un'enumerazione, Result. Il unfrap () è un metodo di supporto per estrarre il risultato effettivo dall'enumerazione. Il metodo di scartare invierà il panico se si verifica un errore.

NOTE - File IO è discusso nel prossimo capitolo.

Argomenti CommandLine

Gli argomenti CommandLine vengono passati a un programma prima di eseguirlo. Sono come i parametri passati alle funzioni. I parametri CommandLine possono essere utilizzati per passare valori alla funzione main () . Ilstd::env::args() restituisce gli argomenti della riga di comando.

Illustrazione

L'esempio seguente passa i valori come argomenti commandLine alla funzione main (). Il programma è stato creato in un nome di file main.rs .

//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
   }
}

Il programma genererà un file main.exe una volta compilato. Più parametri della riga di comando devono essere separati da uno spazio. Esegui main.exe dal terminale come main.exe hello tutorialspoint .

NOTE- hello e tutorialspoint sono argomenti della riga di comando.

Produzione

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

L'output mostra 3 argomenti poiché main.exe è il primo argomento.

Illustrazione

Il seguente programma calcola la somma dei valori passati come argomenti della riga di comando. Un elenco di valori interi separati da uno spazio viene passato al programma.

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);
}

Quando si esegue il programma come main.exe 1 2 3 4, l'output sarà:

No of elements in arguments is :5
sum is 10

Oltre a leggere e scrivere su console, Rust permette di leggere e scrivere su file.

La struttura File rappresenta un file. Consente a un programma di eseguire operazioni di lettura-scrittura su un file. Tutti i metodi nella struttura File restituiscono una variante dell'enumerazione io :: Result.

I metodi comunemente usati della struttura File sono elencati nella tabella seguente:

Suor n Modulo Metodo Firma Descrizione
1 std :: fs :: File Aperto() pub fn open <P: AsRef> (percorso: P) -> Risultato Il metodo statico aperto può essere utilizzato per aprire un file in modalità di sola lettura.
2 std :: fs :: File creare() pub fn create <P: AsRef> (percorso: P) -> Risultato Il metodo statico apre un file in modalità di sola scrittura. Se il file esisteva già, il vecchio contenuto viene distrutto. In caso contrario, viene creato un nuovo file.
3 std :: fs :: remove_file Rimuovi il file() pub fn remove_file <P: AsRef> (percorso: P) -> Risultato <()> Rimuove un file dal filesystem. Non vi è alcuna garanzia che il file venga immediatamente eliminato.
4 std :: fs :: OpenOptions aggiungere() pub fn append (& mut self, append: bool) -> & mut OpenOptions Imposta l'opzione per la modalità di aggiunta del file.
5 std :: io :: scrive write_all () fn write_all (& mut self, buf: & [u8]) -> Risultato <()> Tenta di scrivere un intero buffer in questa scrittura.
6 std :: io :: Leggi read_to_string () fn read_to_string (& mut self, buf: & mut String) -> Risultato Legge tutti i byte fino all'EOF in questa sorgente, aggiungendoli a buf.

Scrivi su un file

Vediamo un esempio per capire come scrivere un file.

Il seguente programma crea un file "data.txt". Il metodo create () viene utilizzato per creare un file. Il metodo restituisce un handle di file se il file viene creato correttamente. L'ultima riga della funzione write_all scriverà i byte nel file appena creato. Se una delle operazioni fallisce, la funzione wait () restituisce un messaggio di errore.

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" );
}

Produzione

data written to file

Leggi da un file

Il seguente programma legge il contenuto in un file data.txt e lo stampa sulla console. La funzione "apri" viene utilizzata per aprire un file esistente. Un percorso assoluto o relativo del file viene passato alla funzione open () come parametro. La funzione open () genera un'eccezione se il file non esiste o se non è accessibile per qualsiasi motivo. Se ha successo, un handle di file a tale file viene assegnato alla variabile "file".

La funzione "read_to_string" dell'handle "file" viene utilizzata per leggere il contenuto di quel file in una variabile stringa.

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);
}

Produzione

Hello World
TutorialsPoint

Elimina un file

L'esempio seguente utilizza la funzione remove_file () per eliminare un file. La funzione wait () restituisce un messaggio personalizzato nel caso si verifichi un errore.

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

Produzione

file is removed

Aggiungi dati a un file

La funzione append () scrive i dati alla fine del file. Questo è mostrato nell'esempio fornito di seguito:

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");
}

Produzione

file append success

Copia un file

L'esempio seguente copia il contenuto di un file in un nuovo 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; }
   }
}

Esegui il programma sopra come main.exe data.txt datacopy.txt . Durante l'esecuzione del file vengono passati due argomenti della riga di comando:

  • il percorso del file di origine
  • il file di destinazione

Cargo è il gestore di pacchetti per RUST. Funziona come uno strumento e gestisce i progetti Rust.

Alcuni comandi cargo di uso comune sono elencati nella tabella seguente:

Suor n Comando e descrizione
1

cargo build

Compila il progetto corrente.

2

cargo check

Analizza il progetto corrente e segnala gli errori, ma non crea file oggetto.

3

cargo run

Compila ed esegue src / main.rs.

4

cargo clean

Rimuove la directory di destinazione.

5

cargo update

Aggiorna le dipendenze elencate in Cargo.lock.

6

cargo new

Crea un nuovo progetto cargo.

Cargo aiuta a scaricare librerie di terze parti. Pertanto, si comporta come un gestore di pacchetti. Puoi anche creare le tue librerie. Cargo viene installato di default quando installi Rust.

Per creare un nuovo progetto cargo, possiamo utilizzare i comandi riportati di seguito.

Crea una cassa binaria

cargo new project_name --bin

Crea una cassa della libreria

cargo new project_name --lib

Per verificare la versione corrente del carico, eseguire il seguente comando:

cargo --version

Illustrazione - Creare un progetto Binary Cargo

Il gioco genera un numero casuale e richiede all'utente di indovinare il numero.

Passaggio 1: creare una cartella del progetto

Apri il terminale e digita il seguente comando cargo new guess-game-app --bin .

Questo creerà la seguente struttura di cartelle.

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

Il comando cargo new viene utilizzato per creare una cassa. Il flag --bin indica che la cassa creata è una cassa binaria. Le casse pubbliche sono archiviate in un repository centrale chiamato crates.iohttps://crates.io/.

Passaggio 2: includere riferimenti a librerie esterne

Questo esempio deve generare un numero casuale. Poiché la libreria standard interna non fornisce la logica di generazione di numeri casuali, è necessario esaminare le librerie o le casse esterne. Usiamorandcrate disponibile sul sito web di crates.io crates.io

Il https://crates.io/crates/randè una libreria ruggine per la generazione di numeri casuali. Rand fornisce utilità per generare numeri casuali, per convertirli in tipi e distribuzioni utili e alcuni algoritmi relativi alla casualità.

Il diagramma seguente mostra il sito Web crate.io e i risultati della ricerca per rand crate.

Copia la versione di rand crate nel file Cargo.toml rand = "0.5.5" .

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

[dependencies]
rand = "0.5.5"

Passaggio 3: compilare il progetto

Vai alla cartella del progetto. Esegui il comandocargo build nella finestra del terminale -

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

La cassa rand e tutte le dipendenze transitive (dipendenze interne di rand) verranno scaricate automaticamente.

Passaggio 4: comprensione della logica di business

Vediamo ora come funziona la logica di business per il gioco di indovinare i numeri -

  • Il gioco genera inizialmente un numero casuale.

  • A un utente viene chiesto di inserire l'input e di indovinare il numero.

  • Se il numero è inferiore al numero generato, viene stampato un messaggio "Troppo basso".

  • Se il numero è maggiore del numero generato, viene stampato un messaggio "Troppo alto".

  • Se l'utente inserisce il numero generato dal programma, il gioco esce.

Passaggio 5: modifica il file main.rs

Aggiungi la logica aziendale al file main.rs.

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;
      }
   }
}

Passaggio 6: compilare ed eseguire il progetto

Esegui il comando cargo run sul terminale. Assicurati che il terminale punti alla directory del progetto.

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 questo capitolo impareremo come funzionano gli iteratori e le chiusure in RUST.

Iteratori

Un iteratore aiuta a iterare su una raccolta di valori come array, vettori, mappe, ecc. Gli iteratori implementano il tratto Iterator definito nella libreria standard Rust. Il metodo iter () restituisce un oggetto iteratore della raccolta. I valori in un oggetto iteratore sono chiamati elementi. Il metodo next () dell'iteratore può essere utilizzato per attraversare gli elementi. Il metodo next () restituisce un valore None quando raggiunge la fine della raccolta.

L'esempio seguente usa un iteratore per leggere i valori da un array.

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());
}

Produzione

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

Se una collezione come array o Vector implementa il tratto Iterator, allora può essere attraversata usando il for ... nella sintassi come mostrato di seguito-

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

Produzione

10 20 30

I 3 metodi seguenti restituiscono un oggetto iteratore da una raccolta, dove T rappresenta gli elementi in una raccolta.

Suor n Metodi e descrizione
1

iter()

fornisce un iteratore su & T (riferimento a T)

2

into_iter()

fornisce un iteratore su T

3

iter_mut()

fornisce un iteratore su & mut T

Illustrazione: iter ()

La funzione iter () utilizza il concetto di prestito. Restituisce un riferimento a ogni elemento della raccolta, lasciando la raccolta intatta e disponibile per il riutilizzo dopo il ciclo.

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
}

Produzione

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

Illustrazione - into_iter ()

Questa funzione utilizza il concetto di proprietà. Sposta i valori nella raccolta in un oggetto iter, ovvero la raccolta viene consumata e non è più disponibile per il riutilizzo.

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
}

Produzione

Hello Kannan
There is a rustacean among us!
Hello Kiran

Illustrazione - for and iter_mut ()

Questa funzione è come la funzione iter () . Tuttavia, questa funzione può modificare gli elementi all'interno della raccolta.

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
}

Produzione

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

Chiusura

La chiusura si riferisce a una funzione all'interno di un'altra funzione. Queste sono funzioni anonime - funzioni senza nome. La chiusura può essere utilizzata per assegnare una funzione a una variabile. Ciò consente a un programma di passare una funzione come parametro ad altre funzioni. La chiusura è anche nota come funzione inline. È possibile accedere alle variabili nella funzione esterna dalle funzioni inline.

Sintassi: definizione di una chiusura

Una definizione di chiusura può facoltativamente avere parametri. I parametri sono racchiusi tra due barre verticali.

let closure_function = |parameter| {
   //logic
}

La sintassi che richiama una chiusura viene implementata Fntratti. Quindi, può essere invocato con() sintassi.

closure_function(parameter);    //invoking

Illustrazione

L'esempio seguente definisce una chiusura is_even all'interno della funzione main () . La chiusura restituisce vero se un numero è pari e restituisce falso se il numero è dispari.

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

Produzione

13 is even ? false

Illustrazione

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

La funzione main () dichiara una variabile val e una chiusura. La chiusura accede alla variabile dichiarata nella funzione esterna main () .

Produzione

12

Rust alloca tutto in pila per impostazione predefinita. Puoi memorizzare le cose nell'heap avvolgendole in puntatori intelligenti come Box . Tipi come Vec e String aiutano implicitamente l'allocazione dell'heap. I puntatori intelligenti implementano i tratti elencati nella tabella seguente. Questi tratti dei puntatori intelligenti li differenziano da una struttura ordinaria -

Suor n Nome del tratto Pacchetto e descrizione
1 Deref

std::ops::Deref

Utilizzato per operazioni di dereferenziazione immutabili, come * v.

2 Far cadere

std::ops::Drop

Utilizzato per eseguire del codice quando un valore esce dall'ambito. Questo a volte è chiamato un distruttore

In questo capitolo impareremo a conoscere il Boxpuntatore intelligente. Impareremo anche come creare un puntatore intelligente personalizzato come Box.

Scatola

Il puntatore intelligente Box, chiamato anche box, consente di archiviare i dati nell'heap anziché nello stack. Lo stack contiene il puntatore ai dati dell'heap. Un Box non ha un sovraccarico delle prestazioni, a parte l'archiviazione dei dati nell'heap.

Vediamo come utilizzare un box per memorizzare un valore i32 sull'heap.

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

Produzione

b = 5

Per accedere a un valore puntato da una variabile, utilizzare dereferencing. * Viene utilizzato come operatore di dereferenziazione. Vediamo come utilizzare la dereferenziazione con Box.

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
}

La variabile x è un tipo di valore con il valore 5. Quindi, l'espressione 5 == x restituirà true. La variabile y punta all'heap. Per accedere al valore in heap, dobbiamo dereferenziare usando * y. * y restituisce il valore 5. Quindi, l'espressione 5 == * y restituisce vero.

Produzione

true
true

Illustrazione - Deref Trait

Il tratto Deref, fornito dalla libreria standard, ci richiede di implementare un metodo chiamato deref , che prende in prestito self e restituisce un riferimento ai dati interni. L'esempio seguente crea una struttura MyBox , che è un tipo generico. Implementa il tratto Deref . Questa caratteristica ci aiuta ad accedere ai valori di heap racchiusi da y usando * y .

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
}

Produzione

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

Illustrazione - Drop Trait

Il tratto Drop contiene il metodo drop () . Questo metodo viene chiamato quando una struttura che ha implementato questo tratto esce dall'ambito. In alcune lingue, il programmatore deve chiamare il codice per liberare memoria o risorse ogni volta che finisce di utilizzare un'istanza di un puntatore intelligente. In Rust, puoi ottenere la deallocazione automatica della memoria usando Drop trait.

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");
}

Nell'esempio precedente, il metodo drop verrà chiamato due volte poiché stiamo creando due oggetti nell'heap.

dropping MyBox object from memory
dropping MyBox object from memory

Nella programmazione simultanea, diverse parti di un programma vengono eseguite in modo indipendente. D'altra parte, nella programmazione parallela, diverse parti di un programma vengono eseguite contemporaneamente. Entrambi i modelli sono ugualmente importanti poiché più computer sfruttano i loro processori multipli.

Discussioni

Possiamo usare i thread per eseguire codici contemporaneamente. Nei sistemi operativi attuali, il codice di un programma eseguito viene eseguito in un processo e il sistema operativo gestisce più processi contemporaneamente. All'interno del tuo programma, puoi anche avere parti indipendenti che vengono eseguite simultaneamente. Le funzionalità che eseguono queste parti indipendenti sono chiamate thread.

Creazione di un thread

Il thread::spawnviene utilizzata per creare un nuovo thread. La funzione spawn prende una chiusura come parametro. La chiusura definisce il codice che dovrebbe essere eseguito dal thread. L'esempio seguente stampa del testo da un thread principale e altro testo da un nuovo thread.

//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));
   }
}

Produzione

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!

Il thread principale stampa i valori da 1 a 4.

NOTE- Il nuovo thread verrà interrotto quando termina il thread principale. L'output di questo programma potrebbe essere leggermente diverso ogni volta.

Il thread::sleepfunzione forza un thread a interrompere la sua esecuzione per un breve periodo, consentendo l'esecuzione di un thread diverso. I thread probabilmente si alterneranno, ma ciò non è garantito: dipende da come il sistema operativo pianifica i thread. In questa esecuzione, il thread principale viene stampato per primo, anche se l'istruzione print dal thread generato viene visualizzata per prima nel codice. Inoltre, anche se il thread generato è programmato per stampare valori fino a 9, è arrivato solo a 5 prima che il thread principale si spenga.

Unisci le maniglie

Un thread generato potrebbe non avere la possibilità di essere eseguito o eseguito completamente. Questo perché il thread principale si completa rapidamente. La funzione spawn <F, T> (f: F) -> JoinHandlelt; T> restituisce JoinHandle. Il metodo join () su JoinHandle attende che il thread associato finisca.

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();
}

Produzione

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!

Il thread principale e il thread generato continuano a passare.

NOTE - Il thread principale attende il completamento del thread generato a causa della chiamata a join() metodo.