Ferrugem - Guia Rápido
Rust é uma linguagem de programação de nível de sistema, desenvolvida por Graydon Hoare. A Mozilla Labs posteriormente adquiriu o programa.
Aplicação v / s Linguagens de Programação de Sistemas
Linguagens de programação de aplicativos como Java / C # são usadas para construir software, que fornece serviços diretamente ao usuário. Eles nos ajudam a construir aplicativos de negócios, como planilhas, processadores de texto, aplicativos da web ou aplicativos móveis.
Linguagens de programação de sistemas como C / C ++ são usadas para construir software e plataformas de software. Eles podem ser usados para construir sistemas operacionais, motores de jogos, compiladores, etc. Essas linguagens de programação requerem um alto grau de interação de hardware.
Sistemas e linguagens de programação de aplicativos enfrentam dois problemas principais -
- É difícil escrever código seguro.
- É difícil escrever código multi-thread.
Por que Rust?
Rust se concentra em três objetivos -
- Safety
- Speed
- Concurrency
A linguagem foi projetada para desenvolver software altamente confiável e rápido de forma simples. Rust pode ser usado para escrever programas de alto nível em programas específicos de hardware.
atuação
A linguagem de programação Rust não possui um Garbage Collector (GC) por design. Isso melhora o desempenho em tempo de execução.
Segurança de memória em tempo de compilação
O software desenvolvido com o Rust está protegido contra problemas de memória, como ponteiros pendentes, saturações de buffer e vazamentos de memória.
Aplicativos multiencadeados
A propriedade do Rust e as regras de segurança de memória fornecem simultaneidade sem disputas de dados.
Suporte para Web Assembly (WASM)
O Web Assembly ajuda a executar algoritmos de alta computação intensiva no navegador, em dispositivos incorporados ou em qualquer outro lugar. Ele roda na velocidade do código nativo. Rust pode ser compilado para Web Assembly para uma execução rápida e confiável.
A instalação do Rust é facilitada por meio rustup, uma ferramenta baseada em console para gerenciar versões Rust e ferramentas associadas.
Instalação em Windows
Vamos aprender como instalar o RUST no Windows.
A instalação do Visual Studio 2013 ou superior com ferramentas C ++ é obrigatória para executar o programa Rust no Windows. Primeiro, baixe o Visual Studio aqui VS 2013 Express
Baixar e instalar rustup ferramenta para janelas. rustup-init.exeestá disponível para download aqui - Rust Lang
Duplo click rustup-init.exeArquivo. Ao clicar, a seguinte tela aparecerá.
Pressione Enter para a instalação padrão. Assim que a instalação for concluída, a tela a seguir aparecerá.
Na tela de instalação, fica claro que os arquivos relacionados ao Rust são armazenados na pasta -
C: \ Users \ {PC} \. Cargo \ bin
O conteúdo da pasta é -
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é o gerenciador de pacotes do Rust. Para verificar secargo está instalado, execute o seguinte comando -
C:\Users\Admin>cargo -V
cargo 1.29.0 (524a578d7 2018-08-05)
O compilador para Rust é rustc. Para verificar a versão do compilador, execute o seguinte comando -
C:\Users\Admin>cargo -V
cargo 1.29.0 (524a578d7 2018-08-05)
Instalação em Linux / Mac
Para instalar rustup no Linux ou macOS, abra um terminal e digite o seguinte comando.
$ curl https://sh.rustup.rs -sSf | sh
O comando baixa um script e inicia a instalação do rustupferramenta, que instala a versão estável mais recente do Rust. Sua senha pode ser solicitada. Se a instalação for bem-sucedida, a seguinte linha aparecerá -
Rust is installed now. Great!
O script de instalação adiciona Rust automaticamente ao PATH do seu sistema após o próximo login. Para começar a usar o Rust imediatamente em vez de reiniciar o terminal, execute o seguinte comando em seu shell para adicionar o Rust ao PATH do sistema manualmente -
$ source $HOME/.cargo/env
Alternativamente, você pode adicionar a seguinte linha ao seu ~ / .bash_profile -
$ export PATH="$HOME/.cargo/bin:$PATH"
NOTE - Quando você tenta compilar um programa Rust e obtém erros indicando que um vinculador não pôde ser executado, isso significa que um vinculador não está instalado em seu sistema e você precisará instalá-lo manualmente.
Usando tutoriais Aponte o terreno de codificação para RUST
Um Loop Read-Evaluate-Print (REPL) é um shell interativo fácil de usar para compilar e executar programas de computador. Se você deseja compilar e executar programas Rust online dentro do navegador, use o Tutorialspoint Coding Ground .
Este capítulo explica a sintaxe básica da linguagem Rust por meio de um HelloWorld exemplo.
Criar uma HelloWorld-App pasta e navegue até essa pasta no terminal
C:\Users\Admin>mkdir HelloWorld-App
C:\Users\Admin>cd HelloWorld-App
C:\Users\Admin\HelloWorld-App>
Para criar um arquivo Rust, execute o seguinte comando -
C:\Users\Admin\HelloWorld-App>notepad Hello.rs
Os arquivos de programa do Rust têm a extensão .rs. O comando acima cria um arquivo vazioHello.rse abre no bloco de notas. Adicione o código fornecido abaixo a este arquivo -
fn
main(){
println!("Rust says Hello to TutorialsPoint !!");
}
O programa acima define uma função main fn main () . A palavra-chave fn é usada para definir uma função. O main () é uma função predefinida que atua como um ponto de entrada para o programa. println! é uma macro predefinida no Rust. É usado para imprimir uma string (aqui Hello) no console. As chamadas de macro são sempre marcadas com um ponto de exclamação - ! .
Compile o Hello.rs arquivo usando rustc.
C:\Users\Admin\HelloWorld-App>rustc Hello.rs
Após a compilação bem-sucedida do programa, um arquivo executável ( file_name.exe ) é gerado. Para verificar se o arquivo .exe foi gerado, execute o seguinte comando.
C:\Users\Admin\HelloWorld-App>dir
//lists the files in folder
Hello.exe
Hello.pdb
Hello.rs
- Execute o arquivo Hello.exe e verifique a saída.
O que é macro?
Rust fornece um sistema macro poderoso que permite meta-programação. Como você viu no exemplo anterior, as macros parecem funções, exceto que seus nomes terminam com um estrondo (!), Mas em vez de gerar uma chamada de função, as macros são expandidas em código-fonte que é compilado com o resto do programa. Portanto, eles fornecem mais recursos de tempo de execução para um programa, ao contrário de funções. Macros são uma versão estendida de funções.
Usando o println! Macro - Sintaxe
println!(); // prints just a newline
println!("hello ");//prints hello
println!("format {} arguments", "some"); //prints format some arguments
Comentários em Rust
Os comentários são uma forma de melhorar a legibilidade de um programa. Os comentários podem ser usados para incluir informações adicionais sobre um programa, como autor do código, dicas sobre uma função / construção, etc. O compilador ignora os comentários.
Rust suporta os seguintes tipos de comentários -
Comentários de linha única (//) - qualquer texto entre // e o final de uma linha é tratado como um comentário
Comentários de várias linhas (/ * * /) - Esses comentários podem abranger várias linhas.
Exemplo
//this is single line comment
/* This is a
Multi-line comment
*/
Executar online
Os programas Rust podem ser executados online através do Tutorialspoint Coding Ground . Escreva o programa HelloWorld na guia Editor e clique em Executar para visualizar o resultado.
O Type System representa os diferentes tipos de valores suportados pelo idioma. O Type System verifica a validade dos valores fornecidos, antes de serem armazenados ou manipulados pelo programa. Isso garante que o código se comporte conforme o esperado. O Type System permite ainda mais dicas de código e documentação automatizada.
Rust é uma linguagem tipificada estaticamente. Cada valor em Rust é de um determinado tipo de dados. O compilador pode inferir automaticamente o tipo de dados da variável com base no valor atribuído a ela.
Declare uma variável
Use o let palavra-chave para declarar uma variável.
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);
}
No exemplo acima, o tipo de dados das variáveis será inferido dos valores atribuídos a elas. Por exemplo, Rust atribuirá tipo de dados de string à variável company_string , tipo de dados float a rating_float , etc.
O println! macro leva dois argumentos -
- Uma sintaxe especial {} , que é o espaço reservado
- O nome da variável ou uma constante
O placeholder será substituído pelo valor da variável
A saída do snippet de código acima será -
company name is: TutorialsPoint
company rating on 5 is:4.5
company is growing: true
company icon is: ♥
Tipos escalares
Um tipo escalar representa um único valor. Por exemplo, 10,3.14, 'c'. A ferrugem tem quatro tipos escalares primários.
- Integer
- Floating-point
- Booleans
- Characters
Aprenderemos sobre cada tipo em nossas seções subsequentes.
Inteiro
Um inteiro é um número sem um componente fracionário. Simplificando, o tipo de dados inteiro é usado para representar números inteiros.
Os inteiros podem ser classificados como assinados e não assinados. Inteiros com sinal podem armazenar valores negativos e positivos. Inteiros sem sinal só podem armazenar valores positivos. Uma descrição detalhada se os tipos inteiros são fornecidos abaixo -
Sr. Não. | Tamanho | Assinado | Sem sinal |
---|---|---|---|
1 | 8 bits | i8 | u8 |
2 | 16 bits | i16 | u16 |
3 | 32 bits | i32 | u32 |
4 | 64 bits | i64 | u64 |
5 | 128 bits | i128 | u128 |
6 | Arco | isize | usize |
O tamanho de um inteiro pode ser arqueado . Isso significa que o tamanho do tipo de dados será derivado da arquitetura da máquina. Um número inteiro cujo tamanho é arch será de 32 bits em uma máquina x86 e 64 bits em uma máquina x64. Um inteiro de arco é usado principalmente para indexar algum tipo de coleção.
Ilustração
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);
}
O resultado será como fornecido abaixo -
result value is 10
sum is -10 and age is 20
mark is 10 and count is 30
O código acima retornará um erro de compilação se você substituir o valor de idade por um valor de ponto flutuante.
Intervalo Inteiro
Cada variante com sinal pode armazenar números de - (2 ^ (n-1) a 2 ^ (n-1) -1 , onde n é o número de bits que a variante usa. Por exemplo, i8 pode armazenar números de - (2 ^ 7) a 2 ^ 7 -1 - aqui substituímos n por 8.
Cada variante sem sinal pode armazenar números de 0 a (2 ^ n) -1 . Por exemplo, u8 pode armazenar números de 0 a 2 ^ 7 , que é igual a 0 a 255.
Estouro inteiro
Um estouro de inteiro ocorre quando o valor atribuído a uma variável de inteiro excede o intervalo definido por Rust para o tipo de dados. Vamos entender isso com um exemplo -
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);
}
O intervalo válido da variável u8 sem sinal é de 0 a 255. No exemplo acima, as variáveis recebem valores maiores que 255 (limite superior para uma variável inteira em Rust). Na execução, o código acima retornará um aviso -warning − literal out of range for u8para peso, altura e variáveis de pontuação. Os valores de estouro após 255 começarão em 0, 1, 2, etc. A saída final sem aviso é conforme mostrado abaixo -
age is 255
weight is 0
height is 1
score is 2
Flutuador
Tipo de dados flutuante em Rust pode ser classificado como f32 e f64. O tipo f32 é um float de precisão simples e f64 tem precisão dupla. O tipo padrão é f64. Considere o exemplo a seguir para entender mais sobre o tipo de dados 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);
}
A saída será como mostrado abaixo -
interest is 8.35
cost is 15000.6
Fundição Automática
A fundição automática não é permitida em Rust. Considere o seguinte trecho de código. Um valor inteiro é atribuído à variável floatinterest.
fn main() {
let interest:f32 = 8; // integer assigned to float variable
println!("interest is {}",interest);
}
O compilador lança um mismatched types error conforme indicado abaixo.
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)
Separador de Número
Para facilitar a leitura de números grandes, podemos usar um separador visual _ sublinhado para separar os dígitos. Isso é 50.000 pode ser escrito como 50_000. Isso é mostrado no exemplo abaixo.
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);
}
O resultado é dado abaixo -
float value 11000.555001
int value 50000
boleano
Os tipos booleanos têm dois valores possíveis - verdadeiro ou falso . Use obool palavra-chave para declarar uma variável booleana.
Ilustração
fn main() {
let isfun:bool = true;
println!("Is Rust Programming Fun ? {}",isfun);
}
A saída do código acima será -
Is Rust Programming Fun ? true
Personagem
O tipo de dados de caractere no Rust suporta números, alfabetos, Unicode e caracteres especiais. Use ocharpalavra-chave para declarar uma variável de tipo de dados de caractere. O tipo de char do Rust representa um valor escalar Unicode, o que significa que pode representar muito mais do que apenas ASCII. Valores escalares Unicode variam deU+0000 para U+D7FF e U+E000 para U+10FFFF inclusive.
Vamos considerar um exemplo para entender mais sobre o tipo de dados 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);
}
A saída do código acima será -
special character is @
alphabet is A
emoji is
Uma variável é um armazenamento nomeado que os programas podem manipular. Simplificando, uma variável ajuda os programas a armazenar valores. Variáveis em Rust são associadas a um tipo de dado específico. O tipo de dados determina o tamanho e o layout da memória da variável, a faixa de valores que podem ser armazenados nessa memória e o conjunto de operações que podem ser realizadas na variável.
Regras para nomear uma variável
Nesta seção, aprenderemos sobre as diferentes regras para nomear uma variável.
O nome de uma variável pode ser composto de letras, dígitos e o caractere de sublinhado.
Deve começar com uma letra ou um sublinhado.
Letras maiúsculas e minúsculas são distintas porque Rust diferencia maiúsculas de minúsculas.
Sintaxe
O tipo de dados é opcional ao declarar uma variável em Rust. O tipo de dados é inferido do valor atribuído à variável.
A sintaxe para declarar uma variável é fornecida abaixo.
let variable_name = value; // no type specified
let variable_name:dataType = value; //type specified
Ilustração
fn main() {
let fees = 25_000;
let salary:f64 = 35_000.00;
println!("fees is {} and salary is {}",fees,salary);
}
A saída do código acima será fees is 25000 and salary is 35000.
Imutável
Por padrão, as variáveis são imutáveis - somente leitura em Rust. Em outras palavras, o valor da variável não pode ser alterado, uma vez que um valor está vinculado a um nome de variável.
Vamos entender isso com um exemplo.
fn main() {
let fees = 25_000;
println!("fees is {} ",fees);
fees = 35_000;
println!("fees changed is {}",fees);
}
A saída será como mostrado abaixo -
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)
A mensagem de erro indica a causa do erro - você não pode atribuir valores duas vezes a taxas variáveis imutáveis. Esta é uma das muitas maneiras pelas quais o Rust permite que os programadores escrevam códigos e aproveita a segurança e a facilidade de simultaneidade.
Mutável
As variáveis são imutáveis por padrão. Prefixe o nome da variável commutpalavra-chave para torná-lo mutável. O valor de uma variável mutável pode ser alterado.
A sintaxe para declarar uma variável mutável é mostrada abaixo -
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);
}
A saída do snippet é fornecida abaixo -
fees is 25000
fees changed is 35000
Constantes representam valores que não podem ser alterados. Se você declarar uma constante, não há como seu valor mudar. A palavra-chave para usar constantes éconst. As constantes devem ser digitadas explicitamente. A seguir está a sintaxe para declarar uma constante.
const VARIABLE_NAME:dataType = value;
Convenção de Nomenclatura Rust Constant
A convenção de nomenclatura para constantes é semelhante à das variáveis. Todos os caracteres em um nome de constante geralmente estão em maiúsculas. Ao contrário de declarar variáveis, olet palavra-chave não é usada para declarar uma constante.
Usamos constantes em Rust no exemplo abaixo -
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
}
Constantes v / s Variáveis
Nesta seção, aprenderemos sobre os fatores de diferenciação entre constantes e variáveis.
Constantes são declaradas usando o const palavra-chave enquanto as variáveis são declaradas usando o let palavra-chave.
Uma declaração de variável pode opcionalmente ter um tipo de dados, enquanto uma declaração de constante deve especificar o tipo de dados. Isso significa que const USER_LIMIT = 100 resultará em um erro.
Uma variável declarada usando o letpalavra-chave é por padrão imutável. No entanto, você tem a opção de alterá-lo usando omutpalavra-chave. As constantes são imutáveis.
As constantes podem ser definidas apenas para uma expressão constante e não para o resultado de uma chamada de função ou qualquer outro valor que será calculado em tempo de execução.
As constantes podem ser declaradas em qualquer escopo, incluindo o escopo global, o que as torna úteis para valores que muitas partes do código precisam conhecer.
Sombreamento de variáveis e constantes
Rust permite aos programadores declarar variáveis com o mesmo nome. Nesse caso, a nova variável substitui a variável anterior.
Vamos entender isso com um exemplo.
fn main() {
let salary = 100.00;
let salary = 1.50 ;
// reads first salary
println!("The value of salary is :{}",salary);
}
O código acima declara duas variáveis pelo nome salário. A primeira declaração recebe 100,00, enquanto a segunda declaração recebe o valor 1,50. A segunda variável sombreia ou oculta a primeira variável enquanto exibe a saída.
Resultado
The value of salary is :1.50
Rust oferece suporte a variáveis com diferentes tipos de dados durante o sombreamento.
Considere o seguinte exemplo.
O código declara duas variáveis pelo nome uname. A primeira declaração é atribuída a um valor de string, enquanto a segunda declaração é atribuída a um inteiro. A função len retorna o número total de caracteres em um valor de string.
fn main() {
let uname = "Mohtashim";
let uname = uname.len();
println!("name changed to integer : {}",uname);
}
Resultado
name changed to integer: 9
Ao contrário das variáveis, as constantes não podem ser sombreadas. Se as variáveis no programa acima forem substituídas por constantes, o compilador gerará um erro.
fn main() {
const NAME:&str = "Mohtashim";
const NAME:usize = NAME.len();
//Error : `NAME` already defined
println!("name changed to integer : {}",NAME);
}
O tipo de dados String em Rust pode ser classificado da seguinte forma -
String Literal(&str)
String Object(String)
String Literal
Literais de string (& str) são usados quando o valor de uma string é conhecido em tempo de compilação. Literais de string são um conjunto de caracteres, que são codificados em uma variável. Por exemplo, let company = "Tutorials Point" . Literais de string são encontrados no módulo std :: str. Literais de string também são conhecidos como fatias de string.
O exemplo a seguir declara dois literais de string - empresa e local .
fn main() {
let company:&str="TutorialsPoint";
let location:&str = "Hyderabad";
println!("company is : {} location :{}",company,location);
}
Literais de string são estáticos por padrão. Isso significa que os literais de string têm garantia de validade durante todo o programa. Também podemos especificar explicitamente a variável como estática, conforme mostrado abaixo -
fn main() {
let company:&'static str = "TutorialsPoint";
let location:&'static str = "Hyderabad";
println!("company is : {} location :{}",company,location);
}
O programa acima irá gerar a seguinte saída -
company is : TutorialsPoint location :Hyderabad
String Object
O tipo de objeto String é fornecido na Biblioteca Padrão. Ao contrário do literal de string, o tipo de objeto de string não faz parte da linguagem central. É definido como uma estrutura pública na biblioteca padrão pub struct String . String é uma coleção que pode ser aumentada. É mutável e do tipo codificado em UTF-8. oStringtipo de objeto pode ser usado para representar valores de string fornecidos em tempo de execução. O objeto String é alocado no heap.
Sintaxe
Para criar um objeto String, podemos usar qualquer uma das seguintes sintaxes -
String::new()
A sintaxe acima cria uma string vazia
String::from()
Isso cria uma string com algum valor padrão passado como parâmetro para o from() método.
O exemplo a seguir ilustra o uso de um objeto 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());
}
O exemplo acima cria duas strings - um objeto de string vazio usando o novo método e um objeto de string do literal de string usando o método from .
A saída é como mostrado abaixo -
length is 0
length is 14
Métodos comuns - objeto string
Sr. Não. | Método | Assinatura | Descrição |
---|---|---|---|
1 | Novo() | pub const fn new () → String | Cria uma nova String vazia. |
2 | para sequenciar() | fn to_string (& self) → String | Converte o valor fornecido em uma String. |
3 | substituir() | pub fn substituir <'a, P> (&' a self, from: P, to: & str) → String | Substitui todas as correspondências de um padrão por outra string. |
4 | as_str () | pub fn as_str (& self) → & str | Extrai uma fatia da string contendo toda a string. |
5 | empurrar() | pub fn push (& mut self, ch: char) | Anexa o caractere fornecido ao final desta String. |
6 | push_str () | pub fn push_str (& mut self, string: & str) | Acrescenta uma determinada fatia de string ao final desta String. |
7 | len () | pub fn len (& self) → usize | Retorna o comprimento desta String, em bytes. |
8 | aparar() | pub fn trim (& self) → & str | Retorna uma fatia de string com espaços em branco à esquerda e à direita removidos. |
9 | split_whitespace () | pub fn split_whitespace (& self) → SplitWhitespace | Divide uma fatia de string por espaço em branco e retorna um iterador. |
10 | Dividido() | pub fn split <'a, P> (&' a self, pat: P) → Split <'a, P>, onde P é o padrão pode ser & str, char ou um fechamento que determina a divisão. | Retorna um iterador sobre substrings desta fatia de string, separados por caracteres correspondidos por um padrão. |
11 | chars () | pub fn chars (& self) → Chars | Retorna um iterador sobre os caracteres de uma fatia de string. |
Ilustração: novo ()
Um objeto de string vazio é criado usando o new()método e seu valor é definido como hello .
fn main(){
let mut z = String::new();
z.push_str("hello");
println!("{}",z);
}
Resultado
O programa acima gera a seguinte saída -
hello
Ilustração: to_string ()
Para acessar todos os métodos do objeto String, converta um literal de string em tipo de objeto usando o to_string() função.
fn main(){
let name1 = "Hello TutorialsPoint ,
Hello!".to_string();
println!("{}",name1);
}
Resultado
O programa acima gera a seguinte saída -
Hello TutorialsPoint , Hello!
Ilustração: substituir ()
o replace()A função tem dois parâmetros - o primeiro parâmetro é um padrão de string a ser pesquisado e o segundo parâmetro é o novo valor a ser substituído. No exemplo acima, Hello aparece duas vezes na string name1 .
A função de substituição substitui todas as ocorrências da string Hello com Howdy.
fn main(){
let name1 = "Hello TutorialsPoint ,
Hello!".to_string(); //String object
let name2 = name1.replace("Hello","Howdy"); //find and replace
println!("{}",name2);
}
Resultado
O programa acima gera a seguinte saída -
Howdy TutorialsPoint , Howdy!
Ilustração: as_str ()
o as_str() função extrai uma fatia de string contendo toda a string.
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);
}
Resultado
O programa acima gera a seguinte saída -
displaying string literal example_string
Ilustração: push ()
o push() função anexa o caractere fornecido ao final desta String.
fn main(){
let mut company = "Tutorial".to_string();
company.push('s');
println!("{}",company);
}
Resultado
O programa acima gera a seguinte saída -
Tutorials
Ilustração: push_str ()
o push_str() função anexa uma determinada fatia de string no final de uma String.
fn main(){
let mut company = "Tutorials".to_string();
company.push_str(" Point");
println!("{}",company);
}
Resultado
O programa acima gera a seguinte saída -
Tutorials Point
Ilustração: len ()
o len() função retorna o número total de caracteres em uma string (incluindo espaços).
fn main() {
let fullname = " Tutorials Point";
println!("length is {}",fullname.len());
}
Resultado
O programa acima gera a seguinte saída -
length is 20
Ilustração: trim ()
A função trim () remove os espaços à esquerda e à direita em uma string. NOTE que esta função não removerá os espaços embutidos.
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());
}
Resultado
O programa acima gera a seguinte saída -
Before trim
length is 24
After trim
length is 15
Ilustração: split_whitespace ()
o split_whitespace()divide a string de entrada em strings diferentes. Ele retorna um iterador, então estamos iterando pelos tokens conforme mostrado abaixo -
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;
}
}
Resultado
token 1 Tutorials
token 2 Point
token 3 has
token 4 good
token 5 tutorials
Ilustração: string split ()
o split() stringmétodo retorna um iterador sobre substrings de uma fatia de string, separados por caracteres correspondidos por um padrão. A limitação do método split () é que o resultado não pode ser armazenado para uso posterior. ocollect O método pode ser usado para armazenar o resultado retornado por split () como um vetor.
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]);
}
O exemplo acima divide a string fullname, sempre que encontrar uma vírgula (,).
Resultado
token is Kannan
token is Sudhakaran
token is Tutorialspoint
firstName is Kannan
lastname is Sudhakaran
company is Tutorialspoint
Ilustração: chars ()
Os caracteres individuais em uma string podem ser acessados usando o método chars. Vamos considerar um exemplo para entender isso.
fn main(){
let n1 = "Tutorials".to_string();
for n in n1.chars(){
println!("{}",n);
}
}
Resultado
T
u
t
o
r
i
a
l
s
Concatenação de Strings com + operador
Um valor de string pode ser anexado a outra string. Isso é chamado de concatenação ou interpolação. O resultado da concatenação de string é um novo objeto de string. O operador + usa internamente um método add . A sintaxe da função add leva dois parâmetros. O primeiro parâmetro é self - o próprio objeto string e o segundo parâmetro é uma referência do segundo objeto string. Isso é mostrado abaixo -
//add function
add(self,&str)->String {
// returns a String object
}
Ilustração: Concatenação de String
fn main(){
let n1 = "Tutorials".to_string();
let n2 = "Point".to_string();
let n3 = n1 + &n2; // n2 reference is passed
println!("{}",n3);
}
O resultado será como fornecido abaixo
TutorialsPoint
Ilustração: Tipo de Fundição
O exemplo a seguir ilustra a conversão de um número em um objeto string -
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");
}
O resultado será como fornecido abaixo
2020
true
Ilustração: Format! Macro
Outra maneira de adicionar objetos String é usar uma função de macro chamada format. O uso de Format! é como mostrado abaixo.
fn main(){
let n1 = "Tutorials".to_string();
let n2 = "Point".to_string();
let n3 = format!("{} {}",n1,n2);
println!("{}",n3);
}
O resultado será como fornecido abaixo
Tutorials Point
Um operador define alguma função que será executada nos dados. Os dados nos quais os operadores trabalham são chamados de operandos. Considere a seguinte expressão -
7 + 5 = 12
Aqui, os valores 7, 5 e 12 são operandos, enquanto + e = são operadores.
Os principais operadores em Rust podem ser classificados como -
- Arithmetic
- Bitwise
- Comparison
- Logical
- Bitwise
- Conditional
Operadores aritméticos
Suponha que os valores nas variáveis aeb são 10 e 5, respectivamente.
Mostrar exemplos
Sr. Não | Operador | Descrição | Exemplo |
---|---|---|---|
1 | + (Adição) | retorna a soma dos operandos | a + b é 15 |
2 | -(Subtração) | retorna a diferença dos valores | ab é 5 |
3 | * (Multiplicação) | retorna o produto dos valores | a * b é 50 |
4 | / (Divisão) | executa a operação de divisão e retorna o quociente | a / b é 2 |
5 | % (Módulo) | executa a operação de divisão e retorna o resto | a% b é 0 |
NOTE - Os operadores ++ e - não são suportados no Rust.
Operadores Relacionais
Operadores relacionais testam ou definem o tipo de relacionamento entre duas entidades. Operadores relacionais são usados para comparar dois ou mais valores. Os operadores relacionais retornam um valor booleano - verdadeiro ou falso.
Suponha que o valor de A seja 10 e B seja 20.
Mostrar exemplos
Sr. Não | Operador | Descrição | Exemplo |
---|---|---|---|
1 | > | Maior que | (A> B) é falso |
2 | < | Menos do que | (A <B) é verdadeiro |
3 | > = | Melhor que ou igual a | (A> = B) é falso |
4 | <= | Menor ou igual a | (A <= B) é verdadeiro |
5 | == | Igualdade | (A == B) é falso |
6 | ! = | Não igual | (A! = B) é verdadeiro |
Operadores lógicos
Operadores lógicos são usados para combinar duas ou mais condições. Os operadores lógicos também retornam um valor booleano. Suponha que o valor da variável A seja 10 e B seja 20.
Mostrar exemplos
Sr. Não | Operador | Descrição | Exemplo |
---|---|---|---|
1 | && (E) | O operador retorna verdadeiro apenas se todas as expressões especificadas retornarem verdadeiro | (A> 10 && B> 10) é False |
2 | || (OU) | O operador retorna verdadeiro se pelo menos uma das expressões especificadas retornar verdadeiro | (A> 10 || B> 10) é verdadeiro |
3 | ! (NÃO) | O operador retorna o inverso do resultado da expressão. Por exemplo:! (> 5) retorna falso | ! (A> 10) é verdadeiro |
Operadores bit a bit
Suponha que a variável A = 2 e B = 3.
Mostrar exemplos
Sr. Não | Operador | Descrição | Exemplo |
---|---|---|---|
1 | & (Bit a bit AND) | Ele executa uma operação booleana AND em cada bit de seus argumentos inteiros. | (A e B) é 2 |
2 | | (BitWise OU) | Ele executa uma operação booleana OR em cada bit de seus argumentos inteiros. | (A | B) é 3 |
3 | ^ (XOR bit a bit) | Ele executa uma operação booleana OU exclusiva em cada bit de seus argumentos inteiros. OR exclusivo significa que o operando um é verdadeiro ou o operando dois é verdadeiro, mas não ambos. | (A ^ B) é 1 |
4 | ! (Bit a bit não) | É um operador unário e opera invertendo todos os bits do operando. | (! B) é -4 |
5 | << (Shift à esquerda) | Ele move todos os bits em seu primeiro operando para a esquerda pelo número de casas especificado no segundo operando. Novos bits são preenchidos com zeros. Deslocar um valor para a esquerda em uma posição é equivalente a multiplicá-lo por 2, deslocar duas posições é equivalente a multiplicar por 4 e assim por diante. | (A << 1) é 4 |
6 | >> (Shift à direita) | Operador binário de deslocamento à direita. O valor do operando esquerdo é movido para a direita pelo número de bits especificado pelo operando direito. | (A >> 1) é 1 |
7 | >>> (deslocamento para a direita com zero) | Este operador é como o operador >>, exceto que os bits deslocados para a esquerda são sempre zero. | (A >>> 1) é 1 |
Estruturas de tomada de decisão requerem que o programador especifique uma ou mais condições a serem avaliadas ou testadas pelo programa, junto com uma instrução ou instruções a serem executadas se a condição for determinada como verdadeira e, opcionalmente, outras instruções a serem executadas se o condição é determinada como falsa.
Abaixo, é mostrada a forma geral de uma estrutura típica de tomada de decisão encontrada na maioria das linguagens de programação -
Sr. Não | Declaração e descrição |
---|---|
1 | if statement Uma instrução if consiste em uma expressão booleana seguida por uma ou mais instruções. |
2 | if...else statement Uma instrução if pode ser seguida por uma instrução else opcional , que é executada quando a expressão booleana é falsa. |
3 | else...if and nested ifstatement Você pode usar uma instrução if ou else if dentro de outra instrução if ou else if . |
4 | match statement Uma declaração de correspondência permite que uma variável seja testada em relação a uma lista de valores. |
Declaração If
A construção if… else avalia uma condição antes que um bloco de código seja executado.
Sintaxe
if boolean_expression {
// statement(s) will execute if the boolean expression is true
}
Se a expressão booleana for avaliada como verdadeira, o bloco de código dentro da instrução if será executado. Se a expressão booleana for avaliada como falsa, o primeiro conjunto de código após o final da instrução if (após a chave de fechamento) será executado.
fn main(){
let num:i32 = 5;
if num > 0 {
println!("number is positive") ;
}
}
O exemplo acima irá imprimir number is positive pois a condição especificada pelo bloco if é verdadeira.
declaração if else
A if pode ser seguido por um opcional elsequadra. O bloco else será executado se a expressão booleana testada pela instrução if for avaliada como falsa.
Sintaxe
if boolean_expression {
// statement(s) will execute if the boolean expression is true
} else {
// statement(s) will execute if the boolean expression is false
}
FlowChart
o ifbloco guarda a expressão condicional. O bloco associado à instrução if é executado se a expressão booleana for avaliada como verdadeira.
O bloco if pode ser seguido por uma instrução else opcional. O bloco de instrução associado ao bloco else é executado se a expressão for avaliada como falsa.
Ilustração - Simples se ... mais
fn main() {
let num = 12;
if num % 2==0 {
println!("Even");
} else {
println!("Odd");
}
}
O exemplo acima imprime se o valor em uma variável é par ou ímpar. O bloco if verifica a divisibilidade do valor por 2 para determinar o mesmo. Aqui está a saída do código acima -
Even
Aninhado se
o else…ifescada é útil para testar várias condições. A sintaxe é mostrada abaixo -
Sintaxe
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
}
Ao usar as instruções if ... else ... if e else, há alguns pontos a serem considerados.
- Um if pode ter zero ou um else e deve vir depois de qualquer else..if.
- Um if pode ter de zero a muitos else..if e eles devem vir antes do else.
- Depois que um else..if for bem-sucedido, nenhum dos outros else..if or else será testado.
Exemplo: senão ... se escada
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) ;
}
}
O snippet exibe se o valor é positivo, negativo ou zero.
Resultado
2 is positive
Declaração de correspondência
A instrução match verifica se um valor atual está correspondendo a uma lista de valores, isso é muito semelhante à instrução switch na linguagem C. Em primeiro lugar, observe que a expressão após a palavra-chave match não precisa ser colocada entre parênteses.
A sintaxe é mostrada abaixo.
let expressionResult = match variable_expression {
constant_expr1 => {
//statements;
},
constant_expr2 => {
//statements;
},
_ => {
//default
}
};
No exemplo abaixo, state_code é combinado com uma lista de valores MH, KL, KA, GA- se qualquer correspondência for encontrada, um valor de string é retornado ao estado variável . Se nenhuma correspondência for encontrada, o caso padrão _ corresponde e o valor Desconhecido é retornado.
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);
}
Resultado
Found match for MH
State name is Maharashtra
Pode haver casos em que um bloco de código precise ser executado repetidamente. Em geral, as instruções de programação são executadas sequencialmente: a primeira instrução em uma função é executada primeiro, seguida pela segunda e assim por diante.
As linguagens de programação fornecem várias estruturas de controle que permitem caminhos de execução mais complicados.
Uma instrução de loop nos permite executar uma instrução ou grupo de instruções várias vezes. A seguir é fornecida a forma geral de uma instrução de loop na maioria das linguagens de programação.
Rust fornece diferentes tipos de loops para lidar com os requisitos de looping -
- while
- loop
- for
Loop Definido
Um loop cujo número de iterações é definido / fixo é denominado como um loop definido. ofor loop é uma implementação de um loop definido.
For Loop
O loop for executa o bloco de código por um número especificado de vezes. Ele pode ser usado para iterar sobre um conjunto fixo de valores, como uma matriz. A sintaxe do loop for é a seguinte
Sintaxe
for temp_variable in lower_bound..upper_bound {
//statements
}
Um exemplo de um loop for é mostrado abaixo
fn main(){
for x in 1..11{ // 11 is not inclusive
if x==5 {
continue;
}
println!("x is {}",x);
}
}
NOTE: que a variável x só está acessível dentro do bloco for.
Resultado
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
Loop indefinido
Um loop indefinido é usado quando o número de iterações em um loop é indeterminado ou desconhecido.
Loops indefinidos podem ser implementados usando -
Sr. Não | Nome e Descrição |
---|---|
1 | While A enquanto ciclo executa as instruções cada vez que a condição especificada for avaliada como True |
2 | Loop O loop é um loop indefinido while (true) |
Ilustração - por enquanto
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);
}
A saída é como mostrado abaixo -
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
Ilustração −loop
fn main(){
//while true
let mut x = 0;
loop {
x+=1;
println!("x={}",x);
if x==15 {
break;
}
}
}
o breakdeclaração é usada para tirar o controle de uma construção. O uso de break em um loop faz com que o programa saia do loop.
Resultado
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
Continue Statement
A instrução continue ignora as instruções subsequentes na iteração atual e leva o controle de volta ao início do loop. Ao contrário da instrução break, o continue não sai do loop. Ele termina a iteração atual e inicia a iteração subsequente.
Um exemplo da instrução continue é fornecido abaixo.
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
}
O exemplo acima exibe o número de valores pares entre 0 e 20. O loop sai da iteração atual se o número for par. Isso é obtido usando a instrução continue.
A contagem de valores ímpares entre 0 e 20 é 10
Funções são os blocos de construção de código legível, sustentável e reutilizável. Uma função é um conjunto de instruções para realizar uma tarefa específica. As funções organizam o programa em blocos lógicos de código. Uma vez definidas, as funções podem ser chamadas para acessar o código. Isso torna o código reutilizável. Além disso, as funções facilitam a leitura e a manutenção do código do programa.
Uma declaração de função informa ao compilador sobre o nome, o tipo de retorno e os parâmetros de uma função. Uma definição de função fornece o corpo real da função.
Sr. Não | Descrição da função |
---|---|
1 | Defining a function A definição da função TA especifica o que e como uma tarefa específica seria realizada. |
2 | Calling or invoking a Function Uma função deve ser chamada para executá-la. |
3 | Returning Functions As funções também podem retornar valor junto com o controle, de volta para o chamador. |
4 | Parameterized Function Os parâmetros são um mecanismo para passar valores para funções. |
Definindo uma função
Uma definição de função especifica o que e como uma tarefa específica seria realizada. Antes de usar uma função, ela deve ser definida. O corpo da função contém código que deve ser executado pela função. As regras para nomear uma função são semelhantes às de uma variável. As funções são definidas usando ofnpalavra-chave. A sintaxe para definir uma função padrão é fornecida abaixo
Sintaxe
fn function_name(param1,param2..paramN) {
// function body
}
Uma declaração de função pode conter opcionalmente parâmetros / argumentos. Os parâmetros são usados para passar valores para funções.
Exemplo - definição de função simples
//Defining a function
fn fn_hello(){
println!("hello from function fn_hello ");
}
Invocando uma função
Uma função deve ser chamada para executá-la. Este processo é denominado comofunction invocation. Os valores dos parâmetros devem ser passados quando uma função é chamada. A função que invoca outra função é chamada decaller function.
Sintaxe
function_name(val1,val2,valN)
Exemplo: invocando uma função
fn main(){
//calling a function
fn_hello();
}
Aqui, o main () é a função do chamador.
Ilustração
O exemplo a seguir define uma função fn_hello(). A função imprime uma mensagem no console. omain()função invoca a função fn_hello () .
fn main(){
//calling a function
fn_hello();
}
//Defining a function
fn fn_hello(){
println!("hello from function fn_hello ");
}
Resultado
hello from function fn_hello
Retornando valor de uma função
As funções também podem retornar um valor junto com o controle, de volta para o chamador. Essas funções são chamadas de funções de retorno.
Sintaxe
Qualquer uma das sintaxes a seguir pode ser usada para definir uma função com tipo de retorno.
Com declaração de retorno
// Syntax1
fn function_name() -> return_type {
//statements
return value;
}
Sintaxe abreviada sem instrução de retorno
//Syntax2
fn function_name() -> return_type {
value //no semicolon means this value is returned
}
ilustração
fn main(){
println!("pi value is {}",get_pi());
}
fn get_pi()->f64 {
22.0/7.0
}
Resultado
pi value is 3.142857142857143
Função com Parâmetros
Os parâmetros são um mecanismo para passar valores para funções. Os parâmetros fazem parte da assinatura da função. Os valores dos parâmetros são passados para a função durante sua invocação. A menos que seja explicitamente especificado, o número de valores passados para uma função deve corresponder ao número de parâmetros definidos.
Os parâmetros podem ser passados para uma função usando uma das seguintes técnicas -
Passe por valor
Quando um método é chamado, um novo local de armazenamento é criado para cada parâmetro de valor. Os valores dos parâmetros reais são copiados para eles. Conseqüentemente, as mudanças feitas no parâmetro dentro do método invocado não têm efeito no argumento.
O exemplo a seguir declara uma variável não, que é inicialmente 5. A variável é passada como parâmetro (por valor) para o mutate_no_to_zero()functionnction, que altera o valor para zero. Após a chamada da função, quando o controle retorna ao método principal, o valor será o mesmo.
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);
}
Resultado
param_no value is :0
The value of no is:5
Passe por referência
Quando você passa parâmetros por referência, ao contrário dos parâmetros de valor, um novo local de armazenamento não é criado para esses parâmetros. Os parâmetros de referência representam o mesmo local da memória que os parâmetros reais fornecidos ao método. Os valores dos parâmetros podem ser passados por referência, prefixando o nome da variável com um& .
No exemplo a seguir, temos uma variável no , que é inicialmente 5. Uma referência à variável no é passada para omutate_no_to_zero()função. A função opera na variável original. Após a chamada da função, quando o controle retornar ao método principal, o valor da variável original será 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
}
O operador * é usado para acessar o valor armazenado na localização da memória que a variável param_noaponta para. Isso também é conhecido como desreferenciamento.
A saída será -
The value of no is 0.
Passando string para uma função
A função main () passa um objeto string para a função 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);
}
Resultado
param_name value is :TutorialsPoint
Tupla é um tipo de dados composto. Um tipo escalar pode armazenar apenas um tipo de dados. Por exemplo, uma variável i32 pode armazenar apenas um único valor inteiro. Em tipos compostos, podemos armazenar mais de um valor por vez e podem ser de tipos diferentes.
As tuplas têm um comprimento fixo - uma vez declaradas, não podem aumentar ou diminuir de tamanho. O índice da tupla começa em0.
Sintaxe
//Syntax1
let tuple_name:(data_type1,data_type2,data_type3) = (value1,value2,value3);
//Syntax2
let tuple_name = (value1,value2,value3);
Ilustração
O exemplo a seguir exibe os valores em uma tupla.
fn main() {
let tuple:(i32,f64,u8) = (-325,4.9,22);
println!("{:?}",tuple);
}
A sintaxe println! ("{}", Tupla) não pode ser usada para exibir valores em uma tupla. Isso ocorre porque uma tupla é um tipo composto. Use a sintaxe println! ("{:?}", tuple_name) para imprimir valores em uma tupla.
Resultado
(-325, 4.9, 22)
Ilustração
O exemplo a seguir imprime valores individuais em uma 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);
}
Resultado
integer is :-325
float is :4.9
unsigned integer is :2
Ilustração
O exemplo a seguir passa uma tupla como parâmetro para uma função. Tuplas são passadas por valor para funções.
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);
}
Resultado
Inside print method
(110, true, 10.9)
Destruindo
A destruição da atribuição é uma característica da ferrugem em que desempacotamos os valores de uma tupla. Isso é feito atribuindo uma tupla a variáveis distintas.
Considere o seguinte exemplo -
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);
}
A variável x é uma tupla atribuída à instrução let. Cada variável - idade, is_male e cgpa conterá os valores correspondentes em uma tupla.
Resultado
Inside print method
Age is 30 , isMale? true,cgpa is 7.9
Neste capítulo, aprenderemos sobre um array e os vários recursos associados a ele. Antes de aprendermos sobre arrays, vejamos como um array é diferente de uma variável.
As variáveis têm as seguintes limitações -
As variáveis são escalares por natureza. Em outras palavras, uma declaração de variável pode conter apenas um único valor por vez. Isso significa que, para armazenar n valores em um programa, será necessária a declaração de n variáveis. Portanto, o uso de variáveis não é viável quando é necessário armazenar uma coleção maior de valores.
As variáveis em um programa são alocadas na memória em ordem aleatória, dificultando a recuperação / leitura dos valores na ordem de sua declaração.
Uma matriz é uma coleção homogênea de valores. Simplificando, uma matriz é uma coleção de valores do mesmo tipo de dados.
Características de um Array
Os recursos de uma matriz estão listados abaixo -
Uma declaração de array aloca blocos de memória sequencial.
Matrizes são estáticas. Isso significa que uma matriz, uma vez inicializada, não pode ser redimensionada.
Cada bloco de memória representa um elemento do array.
Os elementos da matriz são identificados por um número inteiro único denominado subscrito / índice do elemento.
O preenchimento dos elementos da matriz é conhecido como inicialização da matriz.
Os valores dos elementos da matriz podem ser atualizados ou modificados, mas não podem ser excluídos.
Declarando e inicializando matrizes
Use a sintaxe fornecida a seguir para declarar e inicializar uma matriz em Rust.
Sintaxe
//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];
Na primeira sintaxe, o tipo da matriz é inferido a partir do tipo de dados do primeiro elemento da matriz durante a inicialização.
Ilustração: Array Simples
O exemplo a seguir especifica explicitamente o tamanho e o tipo de dados da matriz. A sintaxe {:?} Da função println! () É usada para imprimir todos os valores na matriz. A função len () é usada para calcular o tamanho do array.
fn main(){
let arr:[i32;4] = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
Resultado
array is [10, 20, 30, 40]
array size is :4
Ilustração: Array sem tipo de dados
O programa a seguir declara uma matriz de 4 elementos. O tipo de dados não é especificado explicitamente durante a declaração da variável. Nesse caso, a matriz será do tipo inteiro. A função len () é usada para calcular o tamanho do array.
fn main(){
let arr = [10,20,30,40];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
Resultado
array is [10, 20, 30, 40]
array size is :4
Ilustração: valores padrão
O exemplo a seguir cria uma matriz e inicializa todos os seus elementos com um valor padrão de -1 .
fn main() {
let arr:[i32;4] = [-1;4];
println!("array is {:?}",arr);
println!("array size is :{}",arr.len());
}
Resultado
array is [-1, -1, -1, -1]
array size is :4
Ilustração: Array com for loop
O exemplo a seguir itera por meio de uma matriz e imprime os índices e seus valores correspondentes. O loop recupera valores do índice 0 a 4 (índice do último elemento da matriz).
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]);
}
}
Resultado
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
Ilustração: Usando a função iter ()
A função iter () busca valores de todos os elementos em um 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);
}
}
Resultado
array is [10, 20, 30, 40]
array size is :4
value is :10
value is :20
value is :30
value is :40
Ilustração: array mutável
A palavra-chave mut pode ser usada para declarar um array mutável. O exemplo a seguir declara uma matriz mutável e modifica o valor do segundo elemento da matriz.
fn main(){
let mut arr:[i32;4] = [10,20,30,40];
arr[1] = 0;
println!("{:?}",arr);
}
Resultado
[10, 0, 30, 40]
Passando Arrays como Parâmetros para Funções
Uma matriz pode ser passada por valor ou por referência a funções.
Ilustração: Passar por valor
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);
}
Resultado
Inside update [0, 0, 0]
Inside main [10, 20, 30]
Ilustração: passagem por referência
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);
}
Resultado
Inside update [0, 0, 0]
Inside main [0, 0, 0]
Declaração e constantes de array
Vamos considerar um exemplo fornecido a seguir para entender a declaração e as constantes do array.
fn main() {
let N: usize = 20;
let arr = [0; N]; //Error: non-constant used with constant
print!("{}",arr[10])
}
O compilador resultará em uma exceção. Isso ocorre porque o comprimento de um array deve ser conhecido em tempo de compilação. Aqui, o valor da variável "N" será determinado em tempo de execução. Em outras palavras, as variáveis não podem ser usadas para definir o tamanho de uma matriz.
No entanto, o seguinte programa é válido -
fn main() {
const N: usize = 20;
// pointer sized
let arr = [0; N];
print!("{}",arr[10])
}
O valor de um identificador prefixado com a palavra-chave const é definido em tempo de compilação e não pode ser alterado em tempo de execução. usize tem o tamanho de um ponteiro, portanto, seu tamanho real depende da arquitetura para a qual você está compilando seu programa.
A memória para um programa pode ser alocada da seguinte forma -
- Stack
- Heap
Pilha
Uma pilha segue uma ordem do último na primeira a sair. A pilha armazena valores de dados cujo tamanho é conhecido no momento da compilação. Por exemplo, uma variável de tamanho fixo i32 é candidata à alocação de pilha. Seu tamanho é conhecido em tempo de compilação. Todos os tipos escalares podem ser armazenados na pilha, pois o tamanho é fixo.
Considere um exemplo de string, que recebe um valor no tempo de execução. O tamanho exato de tal string não pode ser determinado em tempo de compilação. Portanto, não é um candidato para alocação de pilha, mas para alocação de heap.
Heap
A memória heap armazena valores de dados cujo tamanho é desconhecido no momento da compilação. É usado para armazenar dados dinâmicos. Simplificando, uma memória heap é alocada para valores de dados que podem mudar ao longo do ciclo de vida do programa. A pilha é uma área da memória menos organizada quando comparada à pilha.
O que é propriedade?
Cada valor em Rust tem uma variável que é chamada ownerdo valor. Cada dado armazenado no Rust terá um proprietário associado a ele. Por exemplo, na sintaxe - seja idade = 30, idade é o dono do valor 30 .
Cada dado pode ter apenas um proprietário por vez.
Duas variáveis não podem apontar para o mesmo local de memória. As variáveis sempre estarão apontando para diferentes locais de memória.
Transferência de propriedade
A propriedade do valor pode ser transferida por -
Atribuindo o valor de uma variável a outra variável.
Passando valor para uma função.
Retornando valor de uma função.
Atribuindo o valor de uma variável a outra variável
O principal ponto de venda do Rust como linguagem é a segurança da memória. A segurança da memória é alcançada por meio de um controle rígido sobre quem pode usar o quê e quando as restrições.
Considere o seguinte snippet -
fn main(){
let v = vec![1,2,3];
// vector v owns the object in heap
//only a single variable owns the heap memory at any given time
let v2 = v;
// here two variables owns heap value,
//two pointers to the same content is not allowed in rust
//Rust is very smart in terms of memory access ,so it detects a race condition
//as two variables point to same heap
println!("{:?}",v);
}
O exemplo acima declara um vetor v. A ideia de propriedade é que apenas uma variável se liga a um recurso, v liga-se ao recurso ou v2liga-se ao recurso. O exemplo acima gera um erro - uso do valor movido: `v` . Isso ocorre porque a propriedade do recurso é transferida para a v2. Isso significa que a propriedade é movida de v para v2 (v2 = v) ev é invalidada após a mudança.
Passando valor para uma função
A propriedade de um valor também muda quando passamos um objeto no heap para um encerramento ou função.
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);
}
Retornando valor de uma função
A propriedade passada para a função será invalidada quando a execução da função for concluída. Uma solução alternativa para isso é permitir que a função retorne o objeto pertencente ao chamador.
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);
}
Propriedade e tipos primitivos
No caso de tipos primitivos, o conteúdo de uma variável é copiado para outra. Portanto, não há nenhuma mudança de propriedade acontecendo. Isso ocorre porque uma variável primitiva precisa de menos recursos do que um objeto. Considere o seguinte exemplo -
fn main(){
let u1 = 10;
let u2 = u1; // u1 value copied(not moved) to u2
println!("u1 = {}",u1);
}
A saída será - 10.
É muito inconveniente passar a propriedade de uma variável para outra função e depois devolver a propriedade. Rust apóia um conceito, empréstimo, onde a propriedade de um valor é transferida temporariamente para uma entidade e então devolvida à entidade proprietária original.
Considere o seguinte -
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);
}
A função principal invoca uma função print_vector () . Um vetor é passado como parâmetro para esta função. A propriedade do vetor também é passada para a função print_vector () do main () . O código acima resultará em um erro conforme mostrado abaixo quando a função main () tenta acessar o vetor v .
| print_vector(v);
| - value moved here
| println!("{}",v[0]);
| ^ value used here after move
Isso ocorre porque uma variável ou valor não pode mais ser usado pela função que originalmente possuía uma vez que a propriedade é transferida para outra função.
O que é empréstimo?
Quando uma função transfere seu controle sobre uma variável / valor para outra função temporariamente, por um tempo, isso é chamado de empréstimo. Isso é conseguido passando uma referência para a variável(& var_name)em vez de passar a própria variável / valor para a função. A propriedade da variável / valor é transferida para o proprietário original da variável após a função para a qual o controle foi passado concluir a execução.
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);
}
Resultado
Inside print_vector function [10, 20, 30]
Printing the value from main() v[0] = 10
Referências mutáveis
Uma função pode modificar um recurso emprestado usando uma referência mutável a tal recurso. Uma referência mutável é prefixada com&mut. As referências mutáveis podem operar apenas em variáveis mutáveis.
Ilustração: mutando uma referência inteira
fn add_one(e: &mut i32) {
*e+= 1;
}
fn main() {
let mut i = 3;
add_one(&mut i);
println!("{}", i);
}
A função main () declara uma variável inteira mutável i e passa uma referência mutável de i para oadd_one(). O add_one () incrementa o valor da variável i em um.
Ilustração: mutando uma referência de string
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
}
A função main () passa uma referência mutável do nome da variável para a função display () . A função de exibição anexa uma string adicional à variável de nome original .
Resultado
param_name value is :TutorialsPoint
The value of name after modification is:TutorialsPoint Rocks
Uma fatia é um ponteiro para um bloco de memória. Fatias podem ser usadas para acessar porções de dados armazenados em blocos de memória contíguos. Ele pode ser usado com estruturas de dados como arrays, vetores e strings. As fatias usam números de índice para acessar partes dos dados. O tamanho de uma fatia é determinado no tempo de execução.
Fatias são indicadores dos dados reais. Eles são passados por referência a funções, o que também é conhecido como empréstimo.
Por exemplo, fatias podem ser usadas para buscar uma parte de um valor de string. Uma string fatiada é um ponteiro para o objeto string real. Portanto, precisamos especificar o índice inicial e final de uma String. O índice começa em 0, assim como os arrays.
Sintaxe
let sliced_value = &data_structure[start_index..end_index]
O valor mínimo do índice é 0 e o valor máximo do índice é o tamanho da estrutura de dados. NOTE que end_index não será incluído na string final.
O diagrama abaixo mostra um exemplo de Tutoriais de string , que tem 9 caracteres. O índice do primeiro caractere é 0 e o do último caractere é 8.
O código a seguir busca 5 caracteres da string (começando no índice 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);
}
Resultado
length of string is 9
rials
Banco de Imagens - Slicing an integer array
A função main () declara um array com 5 elementos. Invoca ouse_slice()função e passa para ela uma fatia de três elementos (aponta para a matriz de dados). As fatias são passadas por referência. A função use_slice () imprime o valor da fatia e seu comprimento.
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);
}
Resultado
length of slice is 3
[20, 30, 40]
Fatias mutáveis
o &mut A palavra-chave pode ser usada para marcar uma fatia como mutável.
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
}
Resultado
length of slice is 3
[20, 30, 40]
[10, 1010, 30, 40, 50]
O código acima passa uma fatia mutável para a função use_slice () . A função modifica o segundo elemento da matriz original.
As matrizes são usadas para representar uma coleção homogênea de valores. Da mesma forma, uma estrutura é outro tipo de dados definido pelo usuário disponível no Rust que nos permite combinar itens de dados de diferentes tipos, incluindo outra estrutura. Uma estrutura define os dados como um par de valores-chave.
Sintaxe - Declarando uma estrutura
A palavra-chave struct é usada para declarar uma estrutura. Como as estruturas são tipadas estaticamente, cada campo na estrutura deve ser associado a um tipo de dados. As regras e convenções de nomenclatura para uma estrutura são como as de uma variável. O bloco de estrutura deve terminar com um ponto e vírgula.
struct Name_of_structure {
field1:data_type,
field2:data_type,
field3:data_type
}
Sintaxe - Inicializando uma estrutura
Depois de declarar uma estrutura, cada campo deve receber um valor. Isso é conhecido como inicialização.
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);
}
O exemplo acima declara uma estrutura Employee com três campos - nome, empresa e idade dos tipos. O main () inicializa a estrutura. Ele usa o println! macro para imprimir os valores dos campos definidos na estrutura.
Output
Name is :Mohtashim company is TutorialsPoint age is 50
Modifying a struct instance
To modify an instance, the instance variable should be marked mutable. The below example declares and initializes a structure named Employee and later modifies value of the age field to 40 from 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);
Output
Name is :Mohtashim company is TutorialsPoint age is 40
Passing a struct to a function
The following example shows how to pass instance of struct as a parameter. The display method takes an Employee instance as parameter and prints the details.
fn display( emp:Employee) {
println!("Name is :{} company is {} age is
{}",emp.name,emp.company,emp.age);
}
Here is the complete program −
//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);
}
Output
Name is :Mohtashim company is TutorialsPoint age is 50
Name is :Kannan company is TutorialsPoint age is 32
Returning struct from a function
Let us consider a function who_is_elder(), which compares two employees age and returns the elder one.
fn who_is_elder (emp1:Employee,emp2:Employee)->Employee {
if emp1.age>emp2.age {
return emp1;
} else {
return emp2;
}
}
Here is the complete program −
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
}
Output
elder is:
Name is :Mohtashim company is TutorialsPoint age is 50
Method in Structure
Methods are like functions. They are a logical group of programming instructions. Methods are declared with the fn keyword. The scope of a method is within the structure block.
Methods are declared outside the structure block. The impl keyword is used to define a method within the context of a structure. The first parameter of a method will be always self, which represents the calling instance of the structure. Methods operate on the data members of a structure.
To invoke a method, we need to first instantiate the structure. The method can be called using the structure's instance.
Syntax
struct My_struct {}
impl My_struct {
//set the method's context
fn method_name() {
//define a method
}
}
Illustration
The following example defines a structure Rectangle with fields − width and height. A method area is defined within the structure's context. The area method accesses the structure's fields via the self keyword and calculates the area of a rectangle.
//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());
}
Output
width is 10 height is 20 area of Rectangle is 200
Static Method in Structure
Static methods can be used as utility methods. These methods exist even before the structure is instantiated. Static methods are invoked using the structure's name and can be accessed without an instance. Unlike normal methods, a static method will not take the &self parameter.
Syntax - Declaring a static method
A static method like functions and other methods can optionally contain parameters.
impl Structure_Name {
//static method that creates objects of the Point structure
fn method_name(param1: datatype, param2: datatype) -> return_type {
// logic goes here
}
}
Syntax - Invoking a static method
The structure_name :: syntax is used to access a static method.
structure_name::method_name(v1,v2)
Illustration
The following example uses the getInstance method as a factory class that creates and returns instances of the structure 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();
}
Output
x =10 y=20
In Rust programming, when we have to select a value from a list of possible variants we use enumeration data types. An enumerated type is declared using the enum keyword. Following is the syntax of enum −
enum enum_name {
variant1,
variant2,
variant3
}
Illustration: Using an Enumeration
The example declares an enum − GenderCategory, which has variants as Male and Female. The print! macro displays value of the enum. The compiler will throw an error the trait std::fmt::Debug is not implemented for GenderCategory. The attribute #[derive(Debug)] is used to suppress this error.
// 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);
}
Output
Male
Female
Struct and Enum
The following example defines a structure Person. The field gender is of the type GenderCategory (which is an enum) and can be assigned either Male or Female as value.
// 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);
}
The example creates objects p1 and p2 of type Person and initializes the attributes, name and gender for each of these objects.
Output
Person { name: "Mohtashim", gender: Male }
Person { name: "Amy", gender: Female }
Option Enum
Option is a predefined enum in the Rust standard library. This enum has two values − Some(data) and None.
Syntax
enum Option<T> {
Some(T), //used to return a value
None // used to return null, as Rust doesn't support
the null keyword
}
Here, the type T represents value of any type.
Rust does not support the null keyword. The value None, in the enumOption, can be used by a function to return a null value. If there is data to return, the function can return Some(data).
Let us understand this with an example −
The program defines a function is_even(), with a return type Option. The function verifies if the value passed is an even number. If the input is even, then a value true is returned, else the function returns 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
}
}
Output
None
Some(true)
Match Statement and Enum
The match statement can be used to compare values stored in an enum. The following example defines a function, print_size, which takes CarType enum as parameter. The function compares the parameter values with a pre-defined set of constants and displays the appropriate message.
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);
}
Output
Large sized Sports Utility car
Small sized car
medium sized car
Match with Option
The example of is_even function, which returns Option type, can also be implemented with match statement as shown below −
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
}
}
Output
not even
Match & Enum with Data Type
It is possible to add data type to each variant of an enum. In the following example, Name and Usr_ID variants of the enum are of String and integer types respectively. The following example shows the use of match statement with an enum having a data type.
// 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);
}
}
}
Output
Name("Mohtashim")
Usr_ID(100)
Mohtashim
A logical group of code is called a Module. Multiple modules are compiled into a unit called crate. Rust programs may contain a binary crate or a library crate. A binary crate is an executable project that has a main() method. A library crate is a group of components that can be reused in other projects. Unlike a binary crate, a library crate does not have an entry point (main() method). The Cargo tool is used to manage crates in Rust. For example, the network module contains networking related functions and the graphics module contains drawing-related functions. Modules are similar to namespaces in other programming languages. Third-party crates can be downloaded using cargo from crates.io.
Sr.No | Term & Description |
---|---|
1 | crate Is a compilation unit in Rust; Crate is compiled to binary or library. |
2 | cargo The official Rust package management tool for crates. |
3 | module Logically groups code within a crate. |
4 | crates.io The official Rust package registry. |
Syntax
//public module
pub mod a_public_module {
pub fn a_public_function() {
//public function
}
fn a_private_function() {
//private function
}
}
//private module
mod a_private_module {
fn a_private_function() {
}
}
Modules can be public or private. Components in a private module cannot be accessed by other modules. Modules in Rust are private by default. On the contrary, functions in a public module can be accessed by other modules. Modules should be prefixed with pub keyword to make it public. Functions within a public module must also be made public.
Illustration: Defining a Module
The example defines a public module − movies. The module contains a function play() that accepts a parameter and prints its value.
pub mod movies {
pub fn play(name:String) {
println!("Playing movie {}",name);
}
}
fn main(){
movies::play("Herold and Kumar".to_string());
}
Output
Playing movie Herold and Kumar
Use Keyword
The use keyword helps to import a public module.
Syntax
use public_module_name::function_name;
Illustration
pub mod movies {
pub fn play(name:String) {
println!("Playing movie {}",name);
}
}
use movies::play;
fn main(){
play("Herold and Kumar ".to_string());
}
Output
Playing movie Herold and Kumar
Nested Modules
Modules can also be nested. The comedy module is nested within the english module, which is further nested in the movies module. The example given below defines a function play inside the movies/english/comedy module.
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());
}
Output
Playing comedy movie Herold and Kumar
Playing comedy movie The Hangover
Playing comedy movie Airplane!
Illustration - Create a Library Crate and Consume in a Binary Crate
Vamos criar uma caixa de biblioteca chamada movie_lib, que contém um módulo movies. Para construir omovie_lib caixa da biblioteca, vamos usar a ferramenta cargo.
Etapa 1 - Criar pasta de projeto
Crie uma pasta movie-app seguida por uma subpasta movie-lib . Depois que a pasta e a subpasta forem criadas, crie umsrcpasta e um arquivo Cargo.toml neste diretório. O código-fonte deve ir para a pasta src . Crie os arquivos lib.rs e movies.rs na pasta src. O arquivo Cargo.toml conterá os metadados do projeto, como número da versão, nome do autor, etc.
A estrutura do diretório do projeto será conforme mostrado abaixo -
movie-app
movie-lib/
-->Cargo.toml
-->src/
lib.rs
movies.rs
Etapa 2 - Edite o arquivo Cargo.toml para adicionar metadados do projeto
[package]
name = "movies_lib"
version = "0.1.0"
authors = ["Mohtashim"]
Etapa 3 - Edite o arquivo lib.rs.
Adicione a seguinte definição de módulo a este arquivo.
pub mod movies;
A linha acima cria um módulo público - movies.
Etapa 4 - Edite o arquivo movies.rs
Este arquivo definirá todas as funções do módulo de filmes.
pub fn play(name:String){
println!("Playing movie {} :movies-app",name);
}
O código acima define uma função play() que aceita um parâmetro e o imprime no console.
Etapa 5 - Construir a caixa da biblioteca
Crie um aplicativo usando o cargo buildcomando para verificar se a caixa da biblioteca está estruturada corretamente. Certifique-se de estar na raiz do projeto - a pasta do aplicativo do filme. A mensagem a seguir será exibida no terminal se a construção for bem-sucedida.
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
Etapa 6 - Criar um aplicativo de teste
Crie outra pasta movie-lib-testna pasta movie-app seguida por um arquivo Cargo.toml e a pasta src. Este projeto deve ter o método principal por se tratar de uma caixa binária, que consumirá a caixa da biblioteca criada anteriormente. Crie um arquivo main.rs na pasta src. A estrutura da pasta será conforme mostrado.
movie-app
movie-lib
// already completed
movie-lib-test/
-->Cargo.toml
-->src/
main.rs
Etapa 7 - Adicione o seguinte no arquivo Cargo.toml
[package]
name = "test_for_movie_lib"
version = "0.1.0"
authors = ["Mohtashim"]
[dependencies]
movies_lib = { path = "../movie-lib" }
NOTE- O caminho para a pasta da biblioteca é definido como dependências. O diagrama a seguir mostra o conteúdo de ambos os projetos.
Etapa 8 - Adicionar o seguinte ao arquivo main.rs
extern crate movies_lib;
use movies_lib::movies::play;
fn main() {
println!("inside main of test ");
play("Tutorialspoint".to_string())
}
O código acima importa um pacote externo chamado movies_lib. Verifique o Cargo.toml do projeto atual para verificar o nome da caixa.
Etapa 9 - Uso de construção e movimentação de carga
Usaremos a construção e execução de carga para construir o projeto binário e executá-lo conforme mostrado abaixo -
A biblioteca de coleção padrão do Rust fornece implementações eficientes das estruturas de dados de programação de propósito geral mais comuns. Este capítulo discute a implementação das coleções comumente usadas - Vector, HashMap e HashSet.
Vetor
Um vetor é uma matriz redimensionável. Ele armazena valores em blocos de memória contíguos. A estrutura predefinida Vec pode ser usada para criar vetores. Algumas características importantes de um vetor são -
Um vetor pode aumentar ou diminuir em tempo de execução.
Um vetor é uma coleção homogênea.
Um vetor armazena dados como sequência de elementos em uma ordem particular. Cada elemento em um vetor é atribuído a um número de índice exclusivo. O índice começa em 0 e vai até n-1, onde, n é o tamanho da coleção. Por exemplo, em uma coleção de 5 elementos, o primeiro elemento estará no índice 0 e o último elemento estará no índice 4.
Um vetor só acrescentará valores ao (ou próximo) ao final. Em outras palavras, um vetor pode ser usado para implementar uma pilha.
A memória para um vetor é alocada no heap.
Sintaxe - Criando um Vetor
let mut instance_name = Vec::new();
O método estático new () da estrutura Vec é usado para criar uma instância de vetor.
Como alternativa, um vetor também pode ser criado usando o vec! macro. A sintaxe é a seguinte -
let vector_name = vec![val1,val2,val3]
A tabela a seguir lista algumas funções comumente usadas da estrutura Vec.
Sr. Não | Método | Assinatura e Descrição |
---|---|---|
1 | Novo() | pub fn new()->Vect Constrói um novo Vec vazio. O vetor não será alocado até que os elementos sejam colocados nele. |
2 | empurrar() | pub fn push(&mut self, value: T) Acrescenta um elemento ao verso de uma coleção. |
3 | retirar() | pub fn remove(&mut self, index: usize) -> T Remove e retorna o elemento no índice de posição dentro do vetor, deslocando todos os elementos após ele para a esquerda. |
4 | contém () | pub fn contains(&self, x: &T) -> bool Retorna verdadeiro se a fatia contiver um elemento com o valor fornecido. |
5 | len () | pub fn len(&self) -> usize Retorna o número de elementos no vetor, também conhecido como 'comprimento'. |
Ilustração: Criando um Vetor - novo ()
Para criar um vetor, usamos o método estático 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);
}
O exemplo acima cria um Vector usando o método estático new () que é definido na estrutura Vec . A função push (val) anexa o valor passado como parâmetro à coleção. A função len () retorna o comprimento do vetor.
Resultado
size of vector is :3
[20, 30, 40]
Ilustração: Criando um Vetor - vec! Macro
O código a seguir cria um vetor usando o comando vec! macro. O tipo de dados do vetor é inferido como o primeiro valor atribuído a ele.
fn main() {
let v = vec![1,2,3];
println!("{:?}",v);
}
Resultado
[1, 2, 3]
Conforme mencionado anteriormente, um vetor só pode conter valores do mesmo tipo de dados. O seguinte snippet gerará um erro [E0308]: erro de tipos incompatíveis .
fn main() {
let v = vec![1,2,3,"hello"];
println!("{:?}",v);
}
Ilustração: push ()
Acrescenta um elemento ao final de uma coleção.
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
v.push(40);
println!("{:?}",v);
}
Resultado
[20, 30, 40]
Ilustração: remove ()
Remove e retorna o elemento no índice de posição dentro do vetor, deslocando todos os elementos após ele para a esquerda.
fn main() {
let mut v = vec![10,20,30];
v.remove(1);
println!("{:?}",v);
}
Resultado
[10, 30]
Ilustração - contém ()
Retorna verdadeiro se a fatia contiver um elemento com o valor fornecido -
fn main() {
let v = vec![10,20,30];
if v.contains(&10) {
println!("found 10");
}
println!("{:?}",v);
}
Resultado
found 10
[10, 20, 30]
Ilustração: len ()
Retorna o número de elementos no vetor, também conhecido como 'comprimento'.
fn main() {
let v = vec![1,2,3];
println!("size of vector is :{}",v.len());
}
Resultado
size of vector is :3
Acessando valores de um vetor
Elementos individuais em um vetor podem ser acessados usando seus números de índice correspondentes. O exemplo a seguir cria um anúncio vetorial e imprime o valor do primeiro elemento.
fn main() {
let mut v = Vec::new();
v.push(20);
v.push(30);
println!("{:?}",v[0]);
}
Output: `20`
Os valores em um vetor também podem ser buscados usando referência à coleção.
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);
}
Resultado
20
30
40
500
[20, 30, 40, 500]
HashMap
Um mapa é uma coleção de pares de valores-chave (chamados de entradas). Duas entradas em um mapa não podem ter a mesma chave. Resumindo, um mapa é uma tabela de consulta. Um HashMap armazena as chaves e os valores em uma tabela hash. As entradas são armazenadas em uma ordem arbitrária. A chave é usada para pesquisar valores no HashMap. A estrutura HashMap é definida nostd::collectionsmódulo. Este módulo deve ser importado explicitamente para acessar a estrutura HashMap.
Sintaxe: Criando um HashMap
let mut instance_name = HashMap::new();
O método estático new () da estrutura HashMap é usado para criar um objeto HashMap. Este método cria um HashMap vazio.
As funções comumente usadas de HashMap são discutidas abaixo -
Sr. Não | Método | Assinatura e Descrição |
---|---|---|
1 | inserir() | pub fn insert(&mut self, k: K, v: V) -> Option Insere um par chave / valor; se nenhuma chave, então Nenhum é retornado. Após a atualização, o valor antigo é retornado. |
2 | len () | pub fn len(&self) -> usize Retorna o número de elementos no mapa. |
3 | pegue() | pub fn get<Q: ?Sized>(&lself, k: &Q) -> Option<&V> where K:Borrow Q:Hash+ Eq Retorna uma referência ao valor correspondente à chave. |
4 | iter () | pub fn iter(&self) -> Iter<K, V> Um iterador que visita todos os pares de valores-chave em ordem arbitrária. O tipo de elemento iterador é (& 'a K, &' a V). |
5 | contém_chave | pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool Retorna verdadeiro se o mapa contém um valor para a chave especificada. |
6 | retirar() | pub fn remove_entry<Q: ?Sized>(&mut self, k: &Q) -> Option<(K, V)> Remove uma chave do mapa, retornando a chave e o valor armazenados se a chave estava anteriormente no mapa. |
Ilustração: inserir ()
Insere um par chave / valor no HashMap.
use std::collections::HashMap;
fn main(){
let mut stateCodes = HashMap::new();
stateCodes.insert("KL","Kerala");
stateCodes.insert("MH","Maharashtra");
println!("{:?}",stateCodes);
}
O programa acima cria um HashMap e o inicializa com 2 pares de valores-chave.
Resultado
{"KL": "Kerala", "MH": "Maharashtra"}
Ilustração: len ()
Retorna o número de elementos no mapa
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());
}
O exemplo acima cria um HashMap e imprime o número total de elementos nele.
Resultado
size of map is 2
Ilustração - get ()
Retorna uma referência ao valor correspondente à chave. O exemplo a seguir recupera o valor da chave KL no 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");
}
}
}
Resultado
size of map is 2
{"KL": "Kerala", "MH": "Maharashtra"}
Value for key KL is Kerala
Ilustração - iter ()
Retorna um iterador contendo referência a todos os pares de valores-chave em uma ordem arbitrária.
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);
}
}
Resultado
key: MH val: Maharashtra
key: KL val: Kerala
Ilustração: contains_key ()
Retorna verdadeiro se o mapa contém um valor para a chave especificada.
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");
}
}
Resultado
found key
Ilustração: remove ()
Remove uma chave do mapa.
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());
}
Resultado
length of the hashmap 3
length of the hashmap after remove() 2
HashSet
HashSet é um conjunto de valores exclusivos do tipo T. Adicionar e remover valores é rápido e é rápido perguntar se um determinado valor está no conjunto ou não. A estrutura HashSet é definida no módulo std :: Collections. Este módulo deve ser importado explicitamente para acessar a estrutura HashSet.
Sintaxe: Criando um HashSet
let mut hash_set_name = HashSet::new();
O método estático, novo , da estrutura HashSet é usado para criar um HashSet. Este método cria um HashSet vazio.
A tabela a seguir lista alguns dos métodos comumente usados da estrutura HashSet.
Sr. Não | Método | Assinatura e Descrição |
---|---|---|
1 | inserir() | pub fn insert(&mut self, value: T) -> bool Adiciona um valor ao conjunto. Se o conjunto não tiver este valor presente, true é retornado, caso contrário, false. |
2 | len () | pub fn len(&self) -> usize Retorna o número de elementos no conjunto. |
3 | pegue() | pub fn get<Q:?Sized>(&self, value: &Q) -> Option<&T> where T: Borrow,Q: Hash + Eq, Retorna uma referência ao valor no conjunto, se houver alguma que seja igual ao valor fornecido. |
4 | iter () | pub fn iter(&self) -> Iter Retorna um iterador visitando todos os elementos em ordem arbitrária. O tipo de elemento iterador é & 'a T. |
5 | contém_chave | pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool Retorna verdadeiro se o conjunto contiver um valor. |
6 | retirar() | pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool Remove um valor do conjunto. Retorna verdadeiro se o valor estava presente no conjunto. |
Ilustração - inserir ()
Adiciona um valor ao conjunto. Um HashSet não adiciona valores duplicados à coleção.
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);
}
Resultado
{"TutorialsPoint", "Kannan", "Mohtashim"}
Ilustração: len ()
Retorna o número de elementos no conjunto.
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());
}
Resultado
size of the set is 3
Ilustração - iter ()
Recupera um iterador visitando todos os elementos em ordem arbitrária.
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);
}
}
Resultado
TutorialsPoint
Mohtashim
Kannan
Ilustração: get ()
Retorna uma referência ao valor no conjunto, se houver, que é igual ao valor fornecido.
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);
}
Resultado
found Mohtashim
{"Kannan", "Mohtashim", "TutorialsPoint"}
Ilustração - contém ()
Retorna verdadeiro se o conjunto contiver um valor.
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");
}
}
Resultado
found name
Ilustração: remove ()
Remove um valor do conjunto.
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());
}
Resultado
length of the Hashset: 3
length of the Hashset after remove() : 2
Em Rust, os erros podem ser classificados em duas categorias principais, conforme mostrado na tabela abaixo.
Sr. Não | Nome e Descrição | Uso |
---|---|---|
1 | Recoverable Erros que podem ser tratados |
Resultado enum |
2 | UnRecoverable Erros que não podem ser tratados |
macro de pânico |
Um erro recuperável é um erro que pode ser corrigido. Um programa pode repetir a operação com falha ou especificar um curso de ação alternativo quando encontra um erro recuperável. Erros recuperáveis não fazem com que um programa falhe abruptamente. Um exemplo de erro recuperável é o erro Arquivo não encontrado .
Erros irrecuperáveis fazem com que um programa falhe abruptamente. Um programa não pode voltar ao seu estado normal se ocorrer um erro irrecuperável. Ele não pode repetir a operação com falha ou desfazer o erro. Um exemplo de erro irrecuperável é tentar acessar um local além do final de uma matriz.
Ao contrário de outras linguagens de programação, Rust não tem exceções. Ele retorna um resultado enum <T, E> para erros recuperáveis, enquanto chama opanicmacro se o programa encontrar um erro irrecuperável. A macro de pânico faz com que o programa seja encerrado abruptamente.
Macro de pânico e erros irrecuperáveis
pânico! macro permite que um programa termine imediatamente e forneça feedback ao chamador do programa. Deve ser usado quando um programa atinge um estado irrecuperável.
fn main() {
panic!("Hello");
println!("End of main"); //unreachable statement
}
No exemplo acima, o programa será encerrado imediatamente quando encontrar o pânico! macro.
Resultado
thread 'main' panicked at 'Hello', main.rs:3
Ilustração: pânico! macro
fn main() {
let a = [10,20,30];
a[10]; //invokes a panic since index 10 cannot be reached
}
A saída é como mostrado abaixo -
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.
Um programa pode invocar o pânico! macro se as regras de negócios forem violadas, conforme mostrado no exemplo abaixo -
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");
}
O exemplo acima retorna um erro se o valor atribuído à variável for ímpar.
Resultado
thread 'main' panicked at 'NOT_AN_EVEN', main.rs:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Resultado Enum e erros recuperáveis
Resultado Enum - <T, E> pode ser usado para tratar erros recuperáveis. Possui duas variantes -OK e Err. T e E são parâmetros de tipo genérico. T representa o tipo de valor que será retornado em um caso de sucesso dentro da variante OK, e E representa o tipo de erro que será retornado em um caso de falha na variante Err.
enum Result<T,E> {
OK(T),
Err(E)
}
Vamos entender isso com a ajuda de um exemplo -
use std::fs::File;
fn main() {
let f = File::open("main.jpg");
//this file does not exist
println!("{:?}",f);
}
O programa retorna OK (Arquivo) se o arquivo já existe e Err (Erro) se o arquivo não for encontrado.
Err(Error { repr: Os { code: 2, message: "No such file or directory" } })
Vamos agora ver como lidar com a variante Err.
O exemplo a seguir lida com um erro retornado ao abrir o arquivo usando o match declaração
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- O programa imprime o final do evento principal , embora o arquivo não tenha sido encontrado. Isso significa que o programa tratou o erro de maneira adequada.
Resultado
file not found
Os { code: 2, kind: NotFound, message: "The system cannot find the file specified." }
end of main
Ilustração
A função is_even retorna um erro se o número não for um número par. A função main () lida com esse erro.
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- Uma vez que a função principal lida com erros normalmente, o final da instrução principal é impresso.
Resultado
Error msg is NOT_AN_EVEN
end of main
desembrulhe () e espere ()
A biblioteca padrão contém alguns métodos auxiliares que ambos os enums - Result <T, E> e Option <T> implementam. Você pode usá-los para simplificar os casos de erro em que você realmente não espera que as coisas falhem. Em caso de sucesso de um método, a função "desembrulhar" é usada para extrair o resultado real.
Sr. Não | Método | Assinatura e Descrição |
---|---|---|
1 | desembrulhar | unwrap(self): T Espera-se estar Ok / Algum e retorna o valor contido em. Se forErr ou None em vez disso, causa pânico com o conteúdo do erro exibido. |
2 | Espero | expect(self, msg: &str): T Comporta-se como desembrulhar, exceto que emite uma mensagem personalizada antes de entrar em pânico, além do conteúdo do erro. |
desembrulhar()
A função unbrap () retorna o resultado real de uma operação bem-sucedida. Ele retorna um pânico com uma mensagem de erro padrão se uma operação falhar. Esta função é uma forma abreviada de declaração de correspondência. Isso é mostrado no exemplo abaixo -
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
Modifique o código acima para passar um número ímpar para o is_even() função.
A função unbrap () entrará em pânico e retornará uma mensagem de erro padrão como mostrado abaixo
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
Espero()
O programa pode retornar uma mensagem de erro personalizada em caso de pânico. Isso é mostrado no seguinte exemplo -
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");
}
A função expect () é semelhante a unbrap (). A única diferença é que uma mensagem de erro personalizada pode ser exibida usando esperar.
Resultado
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.
Os genéricos são uma facilidade para escrever código para vários contextos com diferentes tipos. No Rust, os genéricos referem-se à parametrização de tipos de dados e características. Genéricos permitem escrever código mais conciso e limpo, reduzindo a duplicação de código e fornecendo segurança de tipo. O conceito de Genéricos pode ser aplicado a métodos, funções, estruturas, enumerações, coleções e características.
o <T> syntaxconhecido como parâmetro de tipo, é usado para declarar uma construção genérica. T representa qualquer tipo de dados.
Ilustração: coleção genérica
O exemplo a seguir declara um vetor que pode armazenar apenas inteiros.
fn main(){
let mut vector_integer: Vec<i32> = vec![20,30];
vector_integer.push(40);
println!("{:?}",vector_integer);
}
Resultado
[20, 30, 40]
Considere o seguinte snippet -
fn main() {
let mut vector_integer: Vec<i32> = vec![20,30];
vector_integer.push(40);
vector_integer.push("hello");
//error[E0308]: mismatched types
println!("{:?}",vector_integer);
}
O exemplo acima mostra que um vetor do tipo inteiro só pode armazenar valores inteiros. Portanto, se tentarmos inserir um valor de string na coleção, o compilador retornará um erro. Os genéricos tornam as coleções mais seguras.
Ilustração: Estrutura Genérica
O parâmetro type representa um tipo, que o compilador preencherá posteriormente.
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);
}
O exemplo acima declara uma estrutura genérica chamada Data . O tipo <T> indica algum tipo de dado. A função main () cria duas instâncias - uma instância de inteiro e uma instância de string da estrutura.
Resultado
value is :350
value is :Tom
Traços
As características podem ser usadas para implementar um conjunto padrão de comportamentos (métodos) em várias estruturas. Traços são comointerfacesem Programação Orientada a Objetos. A sintaxe do trait é mostrada abaixo -
Declare uma característica
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
}
}
Os traços podem conter métodos concretos (métodos com corpo) ou métodos abstratos (métodos sem corpo). Use um método concreto se a definição do método for compartilhada por todas as estruturas que implementam a Característica. No entanto, uma estrutura pode optar por substituir uma função definida pelo traço.
Use métodos abstratos se a definição do método variar para as estruturas de implementação.
Sintaxe - Implementar um Traço
impl some_trait for structure_name {
// implement method1() there..
fn method1(&self ){
}
}
Os exemplos a seguir definem uma característica Printable com um método print () , que é implementado pelo livro de estrutura .
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)
}
}
Resultado
Printing book with id:1001 and name Rust in Action
Funções Genéricas
O exemplo define uma função genérica que exibe um parâmetro transmitido a ela. O parâmetro pode ser de qualquer tipo. O tipo do parâmetro deve implementar o traço Display para que seu valor possa ser impresso pelo 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);
}
Resultado
Inside print_pro generic function:
10
Inside print_pro generic function:
20
Inside print_pro generic function:
Hello TutorialsPoint
Este capítulo discute como aceitar valores da entrada padrão (teclado) e exibir valores para a saída padrão (console). Neste capítulo, também discutiremos a passagem de argumentos de linha de comando.
Tipos de leitor e escritor
Os recursos de biblioteca padrão do Rust para entrada e saída são organizados em torno de duas características -
- Read
- Write
Sr. Não | Traço e descrição | Exemplo |
---|---|---|
1 | Read Tipos que implementam Read possuem métodos para entrada orientada a bytes. São chamados de leitores |
Stdin, Arquivo |
2 | Write Tipos que implementam Write suportam saída de texto orientada a byte e UTF-8. Eles são chamados de escritores. |
Stdout, Arquivo |
Ler Traço
Readerssão componentes dos quais seu programa pode ler bytes. Os exemplos incluem a leitura de entrada do teclado, arquivos, etc.read_line() O método desse traço pode ser usado para ler dados, uma linha por vez, de um arquivo ou fluxo de entrada padrão.
Sr. Não | Traço | Método e Descrição |
---|---|---|
1 | Ler | read_line(&mut line)->Result Lê uma linha de texto e a anexa à linha, que é uma String. O valor de retorno é io :: Result, o número de bytes lidos. |
Ilustração - Leitura do console - stdin ()
Os programas Rust podem ter que aceitar valores do usuário em tempo de execução. O exemplo a seguir lê os valores da entrada padrão (teclado) e os imprime no 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);
}
A função stdin () retorna um identificador para o fluxo de entrada padrão do processo atual, ao qual a função read_line pode ser aplicada. Esta função tenta ler todos os caracteres presentes no buffer de entrada quando encontra um caractere de fim de linha.
Resultado
Enter your name :
Mohtashim
Hello , Mohtashim
no of bytes read , 10
Traço de escrita
Writerssão componentes nos quais seu programa pode gravar bytes. Os exemplos incluem a impressão de valores no console, gravação em arquivos, etc. O método write () desta característica pode ser usado para gravar dados em um arquivo ou fluxo de saída padrão.
Sr. Não | Traço | Método e Descrição |
---|---|---|
1 | Escreva | write(&buf)->Result Grava alguns dos bytes no buffer de fatia no fluxo subjacente. Ele retorna um io :: Result, o número de bytes gravados. |
Ilustração - Escrevendo para o console - stdout ()
A impressão! ou println! macros podem ser usadas para exibir texto no console. No entanto, você também pode usar a função de biblioteca padrão write () para exibir algum texto na saída padrão.
Vamos considerar um exemplo para entender isso.
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();
}
Resultado
Tutorials Point
bytes written 15
A função de biblioteca padrão stdout () retorna um identificador para o fluxo de saída padrão do processo atual, para o qual owritefunção pode ser aplicada. O método write () retorna um enum, Result. O unbrap () é um método auxiliar para extrair o resultado real da enumeração. O método de desempacotamento enviará pânico se ocorrer um erro.
NOTE - O arquivo IO é discutido no próximo capítulo.
Argumentos CommandLine
Os argumentos da CommandLine são passados para um programa antes de executá-lo. Eles são como parâmetros passados para funções. Os parâmetros CommandLine podem ser usados para passar valores para a função main () . ostd::env::args() retorna os argumentos da linha de comando.
Ilustração
O exemplo a seguir passa valores como argumentos commandLine para a função main (). O programa é criado em um nome de arquivo 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
}
}
O programa irá gerar um arquivo main.exe uma vez compilado. Vários parâmetros de linha de comando devem ser separados por espaço. Execute main.exe a partir do terminal como main.exe hello tutorialspoint .
NOTE- hello e tutorialspoint são argumentos de linha de comando.
Resultado
No of elements in arguments is :3
[main.exe]
[hello]
[tutorialspoint]
A saída mostra 3 argumentos, já que main.exe é o primeiro argumento.
Ilustração
O programa a seguir calcula a soma dos valores passados como argumentos de linha de comando. Uma lista de valores inteiros separados por espaço é passada ao programa.
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);
}
Ao executar o programa como main.exe 1 2 3 4, a saída será -
No of elements in arguments is :5
sum is 10
Além de ler e gravar no console, o Rust permite ler e gravar arquivos.
A estrutura de arquivo representa um arquivo. Ele permite que um programa execute operações de leitura e gravação em um arquivo. Todos os métodos na estrutura de Arquivo retornam uma variante da enumeração io :: Result.
Os métodos comumente usados da estrutura de arquivo estão listados na tabela abaixo -
Sr. Não | Módulo | Método | Assinatura | Descrição |
---|---|---|---|---|
1 | std :: fs :: File | abrir() | pub fn open <P: AsRef> (caminho: P) -> Resultado | O método open static pode ser usado para abrir um arquivo no modo somente leitura. |
2 | std :: fs :: File | crio() | pub fn create <P: AsRef> (caminho: P) -> Resultado | O método estático abre um arquivo no modo somente gravação. Se o arquivo já existia, o conteúdo antigo é destruído. Caso contrário, um novo arquivo será criado. |
3 | std :: fs :: remove_file | remover arquivo() | pub fn remove_file <P: AsRef> (caminho: P) -> Resultado <()> | Remove um arquivo do sistema de arquivos. Não há garantia de que o arquivo será excluído imediatamente. |
4 | std :: fs :: OpenOptions | acrescentar() | pub fn append (& mut self, append: bool) -> & mut OpenOptions | Define a opção para o modo de acréscimo do arquivo. |
5 | std :: io :: Writes | write_all () | fn write_all (& mut self, buf: & [u8]) -> Resultado <()> | Tenta gravar um buffer inteiro nesta gravação. |
6 | std :: io :: Read | read_to_string () | fn read_to_string (& mut self, buf: & mut String) -> Resultado | Lê todos os bytes até EOF nesta fonte, acrescentando-os ao buf. |
Gravar em um arquivo
Vejamos um exemplo para entender como escrever um arquivo.
O programa a seguir cria um arquivo 'data.txt'. O método create () é usado para criar um arquivo. O método retorna um identificador de arquivo se o arquivo for criado com êxito. A função write_all da última linha gravará bytes no arquivo recém-criado. Se alguma das operações falhar, a função expect () retornará uma mensagem de erro.
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" );
}
Resultado
data written to file
Ler de um arquivo
O programa a seguir lê o conteúdo de um arquivo data.txt e o imprime no console. A função "abrir" é usada para abrir um arquivo existente. Um caminho absoluto ou relativo para o arquivo é passado para a função open () como parâmetro. A função open () lança uma exceção se o arquivo não existir ou se não estiver acessível por qualquer motivo. Se for bem-sucedido, um identificador de arquivo para tal arquivo é atribuído à variável "arquivo".
A função "read_to_string" do identificador "arquivo" é usada para ler o conteúdo desse arquivo em uma variável de string.
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);
}
Resultado
Hello World
TutorialsPoint
Excluir um arquivo
O exemplo a seguir usa a função remove_file () para excluir um arquivo. A função expect () retorna uma mensagem personalizada caso ocorra um erro.
use std::fs;
fn main() {
fs::remove_file("data.txt").expect("could not remove file");
println!("file is removed");
}
Resultado
file is removed
Anexar dados a um arquivo
A função append () grava dados no final do arquivo. Isso é mostrado no exemplo abaixo -
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");
}
Resultado
file append success
Copiar um arquivo
O exemplo a seguir copia o conteúdo de um arquivo para um novo arquivo.
use std::io::Read;
use std::io::Write;
fn main() {
let mut command_line: std::env::Args = std::env::args();
command_line.next().unwrap();
// skip the executable file name
// accept the source file
let source = command_line.next().unwrap();
// accept the destination file
let destination = command_line.next().unwrap();
let mut file_in = std::fs::File::open(source).unwrap();
let mut file_out = std::fs::File::create(destination).unwrap();
let mut buffer = [0u8; 4096];
loop {
let nbytes = file_in.read(&mut buffer).unwrap();
file_out.write(&buffer[..nbytes]).unwrap();
if nbytes < buffer.len() { break; }
}
}
Execute o programa acima como main.exe data.txt datacopy.txt . Dois argumentos de linha de comando são passados durante a execução do arquivo -
- o caminho para o arquivo de origem
- o arquivo de destino
Cargo é o gerenciador de pacotes do RUST. Isso funciona como uma ferramenta e gerencia projetos Rust.
Alguns comandos de carga comumente usados estão listados na tabela abaixo -
Sr. Não | Comando e Descrição |
---|---|
1 | cargo build Compila o projeto atual. |
2 | cargo check Analisa o projeto atual e relata erros, mas não cria arquivos de objeto. |
3 | cargo run Compila e executa src / main.rs. |
4 | cargo clean Remove o diretório de destino. |
5 | cargo update Atualiza as dependências listadas em Cargo.lock. |
6 | cargo new Cria um novo projeto de carga. |
Cargo ajuda a baixar bibliotecas de terceiros. Portanto, ele atua como um gerenciador de pacotes. Você também pode construir suas próprias bibliotecas. O Cargo é instalado por padrão quando você instala o Rust.
Para criar um novo projeto de carga, podemos usar os comandos fornecidos a seguir.
Crie uma caixa binária
cargo new project_name --bin
Crie uma caixa de biblioteca
cargo new project_name --lib
Para verificar a versão atual da carga, execute o seguinte comando -
cargo --version
Ilustração - Criar um projeto Binary Cargo
O jogo gera um número aleatório e solicita que o usuário adivinhe o número.
Etapa 1 - Criar uma pasta de projeto
Abra o terminal e digite o seguinte comando cargo new guess-game-app --bin .
Isso criará a seguinte estrutura de pastas.
guess-game-app/
-->Cargo.toml
-->src/
main.rs
O novo comando de carga é usado para criar uma caixa. O sinalizador --bin indica que a caixa que está sendo criada é uma caixa binária. Caixas públicas são armazenadas em um repositório central chamado crates.iohttps://crates.io/.
Etapa 2 - Incluir referências a bibliotecas externas
Este exemplo precisa gerar um número aleatório. Uma vez que a biblioteca padrão interna não fornece lógica de geração de número aleatório, precisamos olhar para bibliotecas externas ou caixas. Vamos usarrandcaixa que está disponível no site crates.io crates.io
o https://crates.io/crates/randé uma biblioteca de ferrugem para geração de números aleatórios. Rand fornece utilitários para gerar números aleatórios, para convertê-los em tipos e distribuições úteis e alguns algoritmos relacionados à aleatoriedade.
O diagrama a seguir mostra o site crate.io e o resultado da pesquisa por rand crate.
Copie a versão da caixa rand para o arquivo Cargo.toml rand = "0.5.5" .
[package]
name = "guess-game-app"
version = "0.1.0"
authors = ["Mohtashim"]
[dependencies]
rand = "0.5.5"
Etapa 3: compilar o projeto
Navegue até a pasta do projeto. Execute o comandocargo build na janela do terminal -
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
A caixa de rand e todas as dependências transitivas (dependências internas de rand) serão baixadas automaticamente.
Etapa 4 - Compreendendo a lógica de negócios
Vamos agora ver como funciona a lógica de negócios para o jogo de adivinhação de números -
O jogo inicialmente gera um número aleatório.
Um usuário é solicitado a inserir a entrada e adivinhar o número.
Se o número for menor que o número gerado, uma mensagem “Muito baixo” é impressa.
Se o número for maior que o número gerado, uma mensagem “Muito alto” é impressa.
Se o usuário inserir o número gerado pelo programa, o jogo é encerrado.
Etapa 5 - Edite o arquivo main.rs
Adicione a lógica de negócios ao arquivo 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;
}
}
}
Etapa 6 - Compilar e executar o projeto
Execute o comando cargo run no terminal. Certifique-se de que o terminal aponta para o diretório do projeto.
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 ..
Neste capítulo, aprenderemos como iteradores e fechamentos funcionam no RUST.
Iteradores
Um iterador ajuda a iterar sobre uma coleção de valores, como matrizes, vetores, mapas, etc. Os iteradores implementam a característica Iterator que é definida na biblioteca padrão Rust. O método iter () retorna um objeto iterador da coleção. Os valores em um objeto iterador são chamados de itens. O método next () do iterador pode ser usado para percorrer os itens. O método next () retorna um valor Nenhum quando atinge o final da coleção.
O exemplo a seguir usa um iterador para ler valores de uma matriz.
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());
}
Resultado
Iter([10, 20, 30])
Some(10)
Some(20)
Some(30)
None
Se uma coleção como array ou Vector implementa o traço Iterator, então ele pode ser percorrido usando a sintaxe for ... in conforme mostrado abaixo-
fn main() {
let a = [10,20,30];
let iter = a.iter();
for data in iter{
print!("{}\t",data);
}
}
Resultado
10 20 30
Os três métodos a seguir retornam um objeto iterador de uma coleção, onde T representa os elementos de uma coleção.
Sr. Não | Métodos e Descrição |
---|---|
1 | iter() fornece um iterador sobre & T (referência a T) |
2 | into_iter() dá um iterador sobre T |
3 | iter_mut() dá um iterador sobre & mut T |
Ilustração: iter ()
A função iter () usa o conceito de empréstimo. Ele retorna uma referência a cada elemento da coleção, deixando a coleção intacta e disponível para reutilização após o loop.
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
}
Resultado
Hello Kannan
There is a rustacean among us!
Hello Kiran
["Kannan", "Mohtashim", "Kiran"]
Ilustração - into_iter ()
Esta função usa o conceito de propriedade. Ele move os valores da coleção para um objeto iter, ou seja, a coleção é consumida e não está mais disponível para reutilização.
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
}
Resultado
Hello Kannan
There is a rustacean among us!
Hello Kiran
Ilustração - for e iter_mut ()
Esta função é como a função iter () . No entanto, essa função pode modificar os elementos da coleção.
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
}
Resultado
Hello Kannan
There is a rustacean among us!
Hello Kiran
["Kannan", "Mohtashim", "Kiran"]
Fecho
Fechamento se refere a uma função dentro de outra função. Estas são funções anônimas - funções sem um nome. O fechamento pode ser usado para atribuir uma função a uma variável. Isso permite que um programa passe uma função como parâmetro para outras funções. O fechamento também é conhecido como função embutida. As variáveis na função externa podem ser acessadas por funções embutidas.
Sintaxe: Definindo um Fechamento
Uma definição de fechamento pode opcionalmente ter parâmetros. Os parâmetros são colocados em duas barras verticais.
let closure_function = |parameter| {
//logic
}
A sintaxe que invoca um Closure implementa Fntraços. Então, ele pode ser invocado com() sintaxe.
closure_function(parameter); //invoking
Ilustração
O exemplo a seguir define um encerramento is_even dentro da função main () . O encerramento retorna verdadeiro se um número for par e retorna falso se o número for ímpar.
fn main(){
let is_even = |x| {
x%2==0
};
let no = 13;
println!("{} is even ? {}",no,is_even(no));
}
Resultado
13 is even ? false
Ilustração
fn main(){
let val = 10;
// declared outside
let closure2 = |x| {
x + val //inner function accessing outer fn variable
};
println!("{}",closure2(2));
}
A função main () declara uma variável val e um fechamento. O fechamento acessa a variável declarada na função externa main () .
Resultado
12
Rust aloca tudo na pilha por padrão. Você pode armazenar coisas na pilha envolvendo-as em ponteiros inteligentes como Box . Tipos como Vec e String ajudam implicitamente na alocação de heap. Os ponteiros inteligentes implementam as características listadas na tabela abaixo. Essas características dos ponteiros inteligentes os diferenciam de uma estrutura comum -
Sr. Não | Nome do traço | Pacote e descrição |
---|---|---|
1 | Deref | std::ops::Deref Usado para operações de desreferenciamento imutáveis, como * v. |
2 | Solta | std::ops::Drop Usado para executar algum código quando um valor sai do escopo. Isso às vezes é chamado de destruidor |
Neste capítulo, aprenderemos sobre o Boxponteiro inteligente. Também aprenderemos como criar um ponteiro inteligente personalizado como o Box.
Caixa
O ponteiro inteligente Box, também chamado de caixa, permite armazenar dados no heap em vez de na pilha. A pilha contém o ponteiro para os dados do heap. Um Box não tem sobrecarga de desempenho, exceto armazenar seus dados no heap.
Vamos ver como usar uma caixa para armazenar um valor i32 na pilha.
fn main() {
let var_i32 = 5;
//stack
let b = Box::new(var_i32);
//heap
println!("b = {}", b);
}
Resultado
b = 5
Para acessar um valor apontado por uma variável, use dereferencing. O * é usado como um operador de desreferência. Vamos ver como usar a desreferência com o 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
}
A variável x é um tipo de valor com o valor 5. Portanto, a expressão 5 == x retornará verdadeiro. A variável y aponta para a pilha. Para acessar o valor no heap, precisamos cancelar a referência usando * y. * y retorna o valor 5. Portanto, a expressão 5 == * y retorna verdadeiro.
Resultado
true
true
Ilustração - Deref Trait
O traço Deref, fornecido pela biblioteca padrão, requer que implementemos um método denominado deref , que toma emprestado a si mesmo e retorna uma referência aos dados internos. O exemplo a seguir cria uma estrutura MyBox , que é um tipo genérico. Ele implementa o traço Deref . Essa característica nos ajuda a acessar os valores de heap envolvidos por 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
}
Resultado
5==x is true
5==*y is true
x==*y is true
Ilustração - Traço de queda
O traço Drop contém o método drop () . Este método é chamado quando uma estrutura que implementou essa característica sai do escopo. Em algumas linguagens, o programador deve chamar o código para liberar memória ou recursos sempre que terminar de usar uma instância de um ponteiro inteligente. No Rust, você pode obter desalocação automática de memória usando o traço Drop.
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");
}
No exemplo acima, o método drop será chamado duas vezes, pois estamos criando dois objetos no heap.
dropping MyBox object from memory
dropping MyBox object from memory
Na programação simultânea, diferentes partes de um programa são executadas independentemente. Por outro lado, na programação paralela, diferentes partes de um programa são executadas ao mesmo tempo. Ambos os modelos são igualmente importantes à medida que mais computadores tiram proveito de seus múltiplos processadores.
Tópicos
Podemos usar threads para executar códigos simultaneamente. Nos sistemas operacionais atuais, o código de um programa executado é executado em um processo, e o sistema operacional gerencia vários processos ao mesmo tempo. Dentro do seu programa, você também pode ter peças independentes que rodam simultaneamente. Os recursos que executam essas peças independentes são chamados de threads.
Criando um Tópico
o thread::spawnfunção é usada para criar um novo thread. A função spawn leva um encerramento como parâmetro. O encerramento define o código que deve ser executado pelo thread. O exemplo a seguir imprime algum texto de um thread principal e outro texto de um novo 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));
}
}
Resultado
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!
O thread principal imprime valores de 1 a 4.
NOTE- O novo tópico será interrompido quando o tópico principal terminar. A saída deste programa pode ser um pouco diferente a cada vez.
o thread::sleepfunção força um thread a parar sua execução por um curto período, permitindo que um thread diferente seja executado. Os threads provavelmente se revezarão, mas isso não é garantido - depende de como o sistema operacional agenda os threads. Nessa execução, o encadeamento principal é impresso primeiro, embora a instrução de impressão do encadeamento gerado apareça primeiro no código. Além disso, mesmo que a thread gerada seja programada para imprimir valores até 9, ela só chega a 5 antes de a thread principal ser desligada.
Alças de união
Um thread gerado pode não ter a chance de ser executado ou executado completamente. Isso ocorre porque o thread principal é concluído rapidamente. A função spawn <F, T> (f: F) -> JoinHandlelt; T> retorna um JoinHandle. O método join () em JoinHandle aguarda o término do encadeamento associado.
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();
}
Resultado
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!
O thread principal e o thread gerado continuam a alternar.
NOTE - O encadeamento principal aguarda a conclusão do encadeamento gerado por causa da chamada ao join() método.