Conceitos
Dando continuidade ao conhecimento do Rust
, vamos ao capítulo 3 do livro:
The Rust Programming Language
Neste capítulo é visto os conceitos comuns de toda as linguagens, como:
- Variáveis e mutabilidade
- Tipos de dado
- Funções
- Comentários
- Controle de fluxo
Variáveis e Mutabilidade
Imutabilidade
Por padrão, as variáveis são imutáveis. Isso acaba gerando mais segurança na hora de escrever o código, mas ainda existe a opção de criar variáveis mútaveis.
Para exemplificar, criei o projeto variables
, com o comando:
cargo new variables
e criei o seguinte programa:
1 2 3 4 5 6 |
|
ao tentar compilar o programa, o seguinte erro irá aparecer:
error[E0384]: cannot assign twice to immutable variable
x
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment tox
| help: consider making this binding mutable:mut x
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
For more information about this error, tryrustc --explain E0384
.
error: could not compilevariables
due to previous error
O erro é cannot assign twice to immutable variable
, isso quer dizer que uma variável teve seu valor alterado durante a execução do programa. Como por padrão rust
possui variáveis imutáveis, esse erro acabou ocorrendo (linha 6).
Apesar de rust
encorajar o uso de variáveis imutáveis, é possível criar variáveis mutáveis, basta fazer a seguinte alteração no programa:
1 2 3 4 5 6 |
|
Agora, o programa irá compilar e rodar corretamente.
Constantes
Como variáveis imutáveis, as constantes também não podem ser alteradas. Existem algumas diferenças entre elas:
- Não é permitido usar
mut
em constantes - Constantes são declaradas com
const
- O tipo da constante deve ser declarado.
- Constantes podem ser declaradas em qualquer escopo
Um exemplo de declaração de constante:
1 2 3 |
|
Shadowing
É possível declarar variáveis com o mesmo nome, em rust
isso quer dizer que A primeira variável é shadowing pela segunda. Segue um exemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Na linha 5, a variável x
é shadowing pelo da linha 6. Dessa forma, o novo valor de x
é 6. Na linha 8, ocorre o mesmo processo, resultando em x
com valor 12. A diferença é que na linha 8, x
está em outro escopo. Por isso, o valor de x
na linha 12 não é alterado.
O resultado da execução do programa é:
The value of x in the inner scope is: 12
The value of x is: 6
A diferença entre shadowing e variáveis com mut
, é que um erro de compilação vai surgir se o valor da variável for modificada. Com shadowing a variável continua imutável, ou seja, o valor da variável não é simplesmente alterado, ela é totalmente recriada (como uma nova variável) em outra região de memória.
Esse exemplo ajuda a entender:
1 2 3 4 5 |
|
Inicialmente a variável spaces
é uma &str
, mas logo em seguida é um usize
. Essa é uma vantagem do shadowing.
Utilizando o mut
, essa abordagem não é possível:
1 2 3 4 5 |
|
Ao tentar compilar o programa, o seguinte erro aparece:
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected&str
, foundusize
For more information about this error, tryrustc --explain E0308
.
error: could not compilevariables
due to previous error
Tipos de dado
Rust
é uma linguagem estaticamente tipada, isso quer dizer que os tipos de todas as variáveis devem ser conhecidas em tempo de compilação. Neste caso, o compilador irá inferir o tipo do dado. Em casos onde o tipo pode ser ambiguo, é nesserário adicionar o tipo manualmente, por exemplo:
let guess: u32 = "42".parse().expect("Not a number!");
Se o tipo u32
não for declarado, a compilação retorna um erro.
Escalar
O escalar representa um único valor. Rust
possui 4 tipos de principais de escalares:
- interger
- floating-point
- Booleans
- Character
Inteiros
Existem também alguns inteiros built-in:
Tamanho | com sinal | sem sinal |
---|---|---|
8-bits | i8 | u8 |
16-bits | i16 | u16 |
32-bits | i32 (padrão) | u32 |
64-bits | i64 | u64 |
128-bits | i128 | u128 |
arch | isize | usize |
Além disso, é possível escrever inteiros na forma literal de outras formas:
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xFF |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b'A' |
Ponto-Flutuante
Rust
possui 2 tipos primitivos para ponto-flutuante:
- f32 - single-precision
- f64 - double-precision (padrão)
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
Pontos flutuantes em rust são representados de acordo com o IEEE-754.
Operação com números
- Adição
- Subtração
- Multiplicação
- Divisão
- Resto
fn main() {
// Adição
let sum = 5 + 10;
// Subtração
let difference = 95.5 - 4.3;
// Multiplicação
let product = 4 * 30;
// Divisão
let quotient = -1.0 / 2.0;
let truncated = -1 / 2; // Results in 0
// Resto
let remainder = 43 % 5;
}
Boolean
Rust
possui 2 valores possíveis:
- true
- false
Caracteres
Este tipo possui 4 bytes e suporta:
- ASCII
- Accented letters
- Chinese
- Japanese
- Korean characters
- emoji
- Unicode
Alguns exemplos:
fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
}
char literais utilizam aspas simples, ao contrário de strings literais que usam aspas duplas.
Unicode:
U+0000
atéU+D7FF
U+E000
atéU+10FFFF
Composto
Rust
possui 2 tipos basicos de tipos compostos:
- tuples
- arrays
Tuplas
É capaz de juntar diversos tipos em um único tipo composto. Tuplas possuem tamanho fixo, ou seja, uma vez declarado, não pode ser alterado.
fn main() {
let tup: (i32, f64, bool, char) = (500, 6.4, true, '😻');
let (x, y, z, a) = tup; // destructuring
let tup_0 = tup.0;
let tup_1 = tup.1;
let tup_2 = tup.2;
let tup_3 = tup.3;
let unit : ();
}
Uma tupla vazia é chamada de
unit
.
Arrays
Diferente das tuplas, todos os elementos de um arrays
são do mesmo tipo. Também possuem tamanho fixo.
fn main() {
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5]; // Declaring explicit type in initialization
let c = [3; 5]; // Result: [3, 3, 3, 3, 3];
let first_a = a[0];
}
Arrays são uteis quando se quer utilizar a stack ao invés da heap
Funções
Rust
, por convensão, utiliza snake case como estilo para funções e nome de variáveis. As funções podem ser declaradas antes ou depois da main
.
fn other_function(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
fn main() {
println!("Hello, world!");
another_function();
other_function(5, 'h');
}
fn another_function() {
println!("Another function.");
}
Existe uma distinção entre: Declarações e Expressões:
- Statements: Não retornam valores após sua execução.
fn main() {
statement_function();
}
fn statement_function() {
println!("Statement");
}
- Expressions: Retornam algum valor após sua execução.
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {y}");
}
Retorno
Funções que retornam algum valor utilizam a seta (->
) em sua assinatura para determinar o tipo do retorno:
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1
}
Observe que para retorno, não é utilizado o ponto e virgula no final. Caso ele seja colocado o compilador irá retornar um erro.
Comentários
Comentários em rust
utilizam apenas //
.
// This is a
// Multiple Line commnet
fn main() {
// Single line comment
let lucky_number = 7; // I’m feeling lucky today
}
Controle de fluxo
If
Forma mais comum, utilizadas em outras linguagens:
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
Uma forma mais enxuta de if
, é chamada em rust
de let statement:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
Loops com Repetição
loop
fn main() {
loop {
println!("again!");
}
}
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
Quando possui loop
dentro de outro loop
e queremos usar o comandos break
ou continue
, em rust
é possível criar etiquetas para cada loop
:
Loop Label | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
while
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
for
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}