No outro tutorial sobre Rust, compartilhamos nossos pensamentos sobre por que Rust é uma linguagem de programação cada vez mais popular. Também mostramos como escrever o programa Hello World em Rust.
Vamos continuar essa jornada Rust. Neste artigo, apresentaremos variáveis e constantes na linguagem de programação Rust.
Além disso, também abordaremos um novo conceito de programação chamado "shadowing".
A singularidade das variáveis de Rust
Uma variável no contexto de uma linguagem de programação (como Rust) é conhecida como um alias para o endereço de memória no qual alguns dados são armazenados.
Isso vale também para a linguagem de programação Rust. Mas Rust tem uma "característica" única em comparação com a maioria das outras linguagens de programação populares. Cada variável que você declara é imutável por padrão. Isso significa que, uma vez que um valor é atribuído à variável, ele não pode ser alterado.
Essa decisão foi tomada para garantir que, por padrão, você não precise fazer provisões especiais como spin locks ou mutexes para introduzir multi-threading. Rust garante simultaneidade segura. Como todas as variáveis (por padrão) são imutáveis, você não precisa se preocupar com um thread alterando um valor sem saber.
Isso não quer dizer que as variáveis em Rust sejam como constantes porque não são. As variáveis podem ser explicitamente definidas para permitir a mutação. Tal variável é chamada de variável mutável.
A seguir está a sintaxe para declarar uma variável em Rust:
// imutabilidade por padrão
// o valor inicializado é o **único** valor
let nome_da_variavel = valor;
// variáveis mutáveis definidas pelo uso da palavra chave 'mut'
// o valor inicial pode ser mudado para outra coisa
let mut nome_da_variavel = valor;
Embora você tenha permissão para alterar o valor de uma variável mutável, não é possível atribuir o valor de outro tipo de dados a ela.
Ou seja, se você tiver uma variável mutável do tipo float, não poderá atribuir um caractere a ela no futuro.
Visão geral de alto nível dos tipos de dados de Rust
No artigo anterior, você deve ter notado que mencionamos que Rust é uma linguagem fortemente tipada. Mas para definir uma variável, você não especifica o tipo de dados, em vez disso, você usa uma palavra-chave genérica let
.
O compilador Rust pode inferir o tipo de dados de uma variável com base no valor atribuído a ela. Mas isso pode ser feito se você ainda deseja ser explícito com tipos de dados e deseja anotar o tipo. A seguir está a sintaxe:
let nome_da_variavel: tipo_de_dado = valor;
Alguns dos tipos de dados comuns na linguagem de programação Rust são os seguintes:
- Tipo inteiro:
i32
eu32
para inteiros de 32 bits assinados e não assinados, respectivamente - Tipo de ponto flutuante: números de ponto flutuante de
f32
ef64
, 32 bits e 64 bits - Tipo booleano:
bool
- Tipo de caractere:
char
Abordaremos os tipos de dados de Rust com mais detalhes em um próximo artigo. Por enquanto, isso deve ser suficiente.
Rust não tem typecasting implícito. Portanto, se você atribuir o valor 8
a uma variável com um tipo de dados de ponto flutuante, enfrentará um erro de tempo de compilação. O que você deve atribuir em vez disso é o valor 8.
ou 8.0
.
Rust também impõe que uma variável seja inicializada antes que o valor armazenado nela seja lido.
{ // este bloco não vai compilar
let a;
println!("{}", a); // erro nesta linha
// lendo o valor de uma variável **não inicializada** é um erro de tempo de compilação
}
{ // este bloco vai compilar
let a;
a = 128;
println!("{}", a); // sem erro aqui
// variável 'a' tem um valor inicial
}
Se você declarar uma variável sem um valor inicial e usá-la antes de atribuir-lhe algum valor inicial, o compilador Rust lançará um erro de tempo de compilação.
Embora os erros sejam irritantes, nesse caso, o compilador Rust está forçando você a não cometer um dos erros muito comuns que se comete ao escrever código: variáveis não inicializadas.
Mensagens de erro do compilador Rust
Vamos escrever alguns programas onde você
- Entenda o design de Rust executando tarefas "normais", que na verdade são uma das principais causas de problemas relacionados à memória
- Lê e entende as mensagens de erro/aviso do compilador Rust
Testando a imutabilidade de variáveis
Vamos deliberadamente escrever um programa que tenta modificar uma variável mutável e ver o que acontece a seguir.
fn main() {
let mut a = 172;
let b = 273;
println!("a: {a}, b: {b}");
a = 380;
b = 420;
println!("a: {}, b: {}", a, b);
}
Parece um programa simples até agora até a linha 4. Mas na linha 7, a variável b
- uma variável imutável - tem seu valor modificado.
Observe os dois métodos de impressão dos valores das variáveis em Rust. Na linha 4, colocamos as variáveis entre colchetes para que seus valores fossem impressos. Na linha 8, mantemos os colchetes vazios e fornecemos as variáveis como argumentos, estilo C. Ambas as abordagens são válidas. (Exceto para modificar o valor da variável imutável, todos neste programa estão corretos.)
Vamos compilar! Você já sabe como fazer isso se seguiu o artigo anterior.
rustc main.rs
error[E0384]: cannot assign twice to immutable variable `b`
--> main.rs:7:5
|
3 | let b = 273;
| -
| |
| first assignment to `b`
| help: consider making this binding mutable: `mut b`
...
7 | b = 420;
| ^^^^^^^ cannot assign twice to immutable variable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0384`.
A palavra binding
refere-se ao nome da variável. Trata-se, porém, de uma simplificação excessiva. Isso demonstra perfeitamente a verificação de erros robusta e as mensagens de erro informativas do Rust. A primeira linha lê a mensagem de erro que impede a compilação do código acima:
error[E0384]: cannot assign twice to immutable variable b
Isso significa que o compilador do Rust notou que estávamos tentando reatribuir um novo valor à variável b
mas a variável b
é uma variável imutável. Então isso está causando esse erro.
O compilador ainda identifica os números exatos de linha e coluna onde esse erro é encontrado.
Sob a linha que diz first assignment to 'b'
é a linha que fornece ajuda. Como estamos mutando o valor da variável imutável b
, somos instruídos a declarar a variável b
como uma variável mutável usando a palavra-chave mut
.
Entendendo melhor variáveis não inicializadas
Agora, vamos ver o que o compilador Rust faz quando o valor de uma variável não inicializada é lido.
fn main() {
let a: i32;
a = 123;
println!("a: {a}");
let b: i32;
println!("b: {b}");
b = 123;
}
Aqui, tenho duas variáveis imutáveis a
e b
e ambas não são inicializadas no momento da declaração. A variável a
obtém um valor atribuído antes que seu valor seja lido. Mas o valor da variável b
é lido antes de ser atribuído um valor inicial.
Vamos compilar e ver o resultado.
rustc main.rs
warning: value assigned to `b` is never read
--> main.rs:8:5
|
8 | b = 123;
| ^
|
= help: maybe it is overwritten before being read?
= note: `#[warn(unused_assignments)]` on by default
error[E0381]: used binding `b` is possibly-uninitialized
--> main.rs:7:19
|
6 | let b: i32;
| - binding declared here but left uninitialized
7 | println!("b: {b}");
| ^ `b` used here but it is possibly-uninitialized
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0381`.
Aqui, o compilador Rust lança um erro de tempo de compilação e um aviso. O aviso diz que o valor da variável b
nunca está sendo lido.
Mas isso é um absurdo! O valor da variável b
está sendo acessado na linha 7. Mas olhe com atenção; O alerta é em relação à linha 8. Isso é confuso; Vamos ignorar temporariamente esse aviso e passar para o erro.
A mensagem de erro lê que used binding
bis possibly-uninitialized
. Como no exemplo anterior, o compilador Rust está apontando que o erro é causado pela leitura do valor da variável b
na linha 7. A razão pela qual a leitura do valor da variável b
é um erro é que seu valor não é inicializado. Na linguagem de programação Rust, isso é ilegal. Daí o erro de tempo de compilação.
Este erro pode ser facilmente resolvido trocando os códigos das linhas 7 e 8. Faça isso e veja se o erro desaparece.
Exemplo de programa: Trocar números
Agora que você está familiarizado com os problemas comuns relacionados a variáveis, vamos examinar um programa que troca os valores de duas variáveis.
fn main() {
let mut a = 7186932;
let mut b = 1276561;
println!("a: {a}, b: {b}");
// trocando os valores
let temp = a;
a = b;
b = temp;
println!("a: {}, b: {}", a, b);
}
Aqui, declaramos duas variáveis, a
e b
. Ambas as variáveis são mutáveis porque desejamos mudar seus valores no futuro. Atribuímos alguns valores aleatórios. Inicialmente, imprimimos os valores dessas variáveis.
Então, na linha 8, criamos uma variável imutável chamada temp
e atribuo a ela o valor armazenado em a
. A razão pela qual essa variável é imutável é porque o valor de temp
não será alterado.
Para trocar valores, atribuo o valor da variável b
à variável a
e, na próxima linha, atribuo o valor de temp
(que contém o valor de a
) à variável b
. Agora que os valores são trocados, imprimo valores de variáveis a
e b
.
Quando o código acima é compilado e executado, obtenho a seguinte saída:
a: 7186932, b: 1276561
a: 1276561, b: 7186932
Como você pode ver, os valores são trocados. Perfeito.
Usando variáveis não utilizadas
Quando você tiver declarado algumas variáveis que pretende usar no futuro, mas ainda não as usou, e compilar seu código Rust para verificar algo, o compilador Rust irá avisá-lo sobre isso.
A razão para isso é óbvia. As variáveis que não serão usadas ocupam tempo de inicialização desnecessário (ciclo da CPU) e espaço de memória. Se ele não será usado, por que tê-lo em seu programa em primeiro lugar? No entanto, o compilador otimiza isso. Mas ainda continua sendo um problema em termos de legibilidade em forma de excesso de código.
Mas, às vezes, você pode estar em uma situação em que a criação de uma variável pode não estar em suas mãos. Digamos quando uma função retorna mais de um valor e você só precisa de alguns valores. Nesse caso, você não pode dizer ao mantenedor da biblioteca para ajustar sua função de acordo com suas necessidades.
Então, em momentos como esse, você pode ter uma variável que começa com um sublinhado (_
) e o compilador do Rust não lhe dará mais tais avisos. E se você realmente não precisa nem mesmo usar o valor armazenado na referida variável não utilizada, você pode simplesmente nomeá-lo _
(sublinhado) e o compilador Rust irá ignorá-lo também!
O seguinte programa não só não irá gerar qualquer saída, mas também não irá gerar quaisquer avisos e / ou mensagens de erro:
fn main() {
let _unnecessary_var = 0; // sem avisos
let _ = 0.0; // ignorado completamente
}
Operações aritméticas
Como matemática é matemática, Rust não inova nisso. Você pode usar todos os operadores aritméticos que você pode ter usado em outras linguagens de programação como C, C++ e/ou Java.
Uma lista completa de todas as operações na linguagem de programação Rust, juntamente com seu significado, pode ser encontrada aqui.
Exemplo de programa: Um termômetro em Rust
A seguir está um programa típico que converte Fahrenheit em Celsius e vice-versa.
fn main() {
let boiling_water_f: f64 = 212.0;
let frozen_water_c: f64 = 0.0;
let boiling_water_c = (boiling_water_f - 32.0) * (5.0 / 9.0);
let frozen_water_f = (frozen_water_c * (9.0 / 5.0)) + 32.0;
println!(
"Água começa a ferver em {}°C (ou {}°F).",
boiling_water_c, boiling_water_f
);
println!(
"Água começa a congelar em {}°C (ou {}°F).",
frozen_water_c, frozen_water_f
);
}
Não está acontecendo muito aqui... A temperatura de Fahrenheit é convertida em Celsius e vice-versa para a temperatura em Celsius.
Como você pode ver aqui, como Rust não permite a fundição automática de tipos, tivemos que introduzir um ponto decimal para os números inteiros 32, 9 e 5. Fora isso, isso é semelhante ao que você faria em C, C++ e/ou Java.
Como um exercício de aprendizado, tente escrever um programa que descubra quantos dígitos estão em um determinado número.
Constantes
Com algum conhecimento de programação, você pode saber o que isso significa. Uma constante é um tipo especial de variável cujo valor nunca muda. Mantém-se constante.
Na linguagem de programação Rust, uma constante é declarada usando a seguinte sintaxe:
const NOME_DA_CONSTANTE: tipo_de_dado = valor;
Como você pode ver, a sintaxe para declarar uma constante é muito semelhante ao que vimos ao declarar uma variável em Rust. No entanto, há duas diferenças:
- Um nome constante deve estar em
SCREAMING_SNAKE_CASE
. Todos os caracteres maiúsculos e palavras separados por uma minúscula. - Anotar o tipo de dados da constante é necessário.
Variáveis versus Constantes
Você deve estar se perguntando, já que as variáveis são imutáveis por padrão, por que a linguagem também incluiria constantes?
A tabela a seguir deve ajudar a tirar suas dúvidas.
Exemplo de programa usando constantes: Calcular área do círculo
A seguir está um programa direto sobre constantes em Rust. Ele calcula a área e o perímetro de um círculo.
fn main() {
const PI: f64 = 3.14;
let radius: f64 = 50.0;
let circle_area = PI * (radius * radius);
let circle_perimeter = 2.0 * PI * radius;
println!("Há um círculo com o raio de {radius} centímetros.");
println!("É uma área de {} centímetros quadrados.", circle_area);
println!(
"E tem uma circunferência de {} centímetros.",
circle_perimeter
);
}
E ao executar o código, a seguinte saída é produzida:
Há um círculo com o raio de 50 centímetros.
É uma área de 7850 centímetros quadrados.
E tem uma circunferência de 314 centímetros.
Sombreamento variável em Rust
Se você é um programador de C++, você já meio que sabe a que estamos nos referindo. Quando o programador declara uma nova variável com o mesmo nome de uma variável já declarada, ela é conhecida como sombreamento de variável.
Ao contrário do C++, Rust permite que você execute sombreamento variável no mesmo escopo também!
Quando um programador faz sombra a uma variável existente, a nova variável recebe um novo endereço de memória, mas é referida com o mesmo nome da variável existente. Vamos dar uma olhada em como funciona em Rust.
fn main() {
let a = 108;
println!("endereço de a: {:p}, valor de a: {a}", &a);
let a = 56;
println!("endereço de a: {:p}, valor de a: {a} // post shadowing", &a);
let mut b = 82;
println!("\nendereço de b: {:p}, valor de b: {b}", &b);
let mut b = 120;
println!("addr of b: {:p}, valor de b: {b} // post shadowing", &b);
let mut c = 18;
println!("\nendereço de c: {:p}, valor de c: {c}", &c);
c = 29;
println!("endereço de c: {:p}, valor de c: {c} // post shadowing", &c);
}
O :p
dentro de colchetes na instrução println
é semelhante ao uso %p
em C. Ele especifica que o valor está no formato de um endereço de memória (ponteiro).
Eu pego 3 variáveis aqui. A variável a
é imutável e é sombreada na linha 4. A variável b
é mutável e também é sombreada na linha 9. A variável c
é mutável, mas na linha 14, apenas seu valor é mutado. Não é sombra.
Agora, vamos olhar para a saída.
endereço de a: 0x7ffe954bf614, valor de a: 108
endereço de a: 0x7ffe954bf674, valor de a: 56 // post shadowing
endereço de b: 0x7ffe954bf6d4, valor de b: 82
endereço de b: 0x7ffe954bf734, valor de b: 120 // post shadowing
endereço de c: 0x7ffcfcd16b54, valor de c: 18
endereço de c: 0x7ffcfcd16b54, valor de c: 29 // post shadowing
Olhando para a saída, você pode ver que não apenas os valores de todas as três variáveis foram alterados, mas os endereços das variáveis que foram sombreadas também são diferentes (verifique os últimos caracteres hexadecimais).
O endereço de memória para as variáveis a
e b
alterado. Isso significa que a mutabilidade, ou a falta dela, de uma variável não é uma restrição ao sombrear uma variável.
Conclusão
Este artigo aborda variáveis e constantes na linguagem de programação Rust. Operações aritméticas também são abordadas.
Como recapitulação:
- As variáveis em Rust são imutáveis por padrão, mas a mutabilidade pode ser introduzida.
- O programador precisa especificar explicitamente a mutabilidade da variável.
- As constantes são sempre imutáveis, não importa o que aconteça e exigem anotação de tipo.
- Sombreamento de variável é declarar uma variável nova com o mesmo nome de uma variável existente.
Em um próximo tutorial, vamos falar sobre tipos de dados em Rust. Fique atento.