Variáveis e Mutabilidade
Como mencionado na seção “Armazenando valores com variáveis”, por padrão, as variáveis são imutáveis. Esse é um dos vários empurrões que o Rust dá para que você escreva código tirando proveito da segurança e da facilidade de concorrência que a linguagem oferece. Ainda assim, você continua tendo a opção de tornar suas variáveis mutáveis. Vamos explorar como e por que Rust incentiva você a preferir a imutabilidade e por que, às vezes, pode fazer sentido abrir mão disso.
Quando uma variável é imutável, depois que um valor é associado a um nome, você
não pode alterar esse valor. Para ilustrar isso, gere um novo projeto chamado
variables dentro do diretório projects usando cargo new variables.
Depois, no novo diretório variables, abra src/main.rs e substitua seu código pelo seguinte, que ainda não compilará:
Nome do arquivo: src/main.rs
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
Salve e execute o programa com cargo run. Você deverá receber uma mensagem de
erro sobre imutabilidade, como mostrado nesta saída:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| - first assignment to `x`
3 | println!("The value of x is: {x}");
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
|
help: consider making this binding mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Este exemplo mostra como o compilador ajuda você a encontrar erros em seus programas. Erros de compilação podem ser frustrantes, mas, na verdade, eles só significam que seu programa ainda não está fazendo com segurança aquilo que você quer; eles não significam que você é um mau programador! Rustaceanos experientes também recebem erros do compilador.
Você recebeu a mensagem de erro cannot assign twice to immutable variable `x` porque tentou atribuir um segundo valor à variável imutável x.
É importante recebermos erros em tempo de compilação quando tentamos mudar um valor marcado como imutável, porque exatamente esse tipo de situação pode levar a bugs. Se uma parte do código opera supondo que um valor nunca vai mudar e outra parte muda esse valor, é possível que a primeira parte do código não faça o que foi projetada para fazer. A causa desse tipo de bug pode ser difícil de rastrear depois, principalmente quando a segunda parte do código muda o valor apenas às vezes. O compilador Rust garante que, quando você afirma que um valor não vai mudar, ele realmente não vai mudar, então você não precisa ficar acompanhando isso manualmente. Isso torna o código mais fácil de entender.
Mas a mutabilidade pode ser muito útil e pode tornar o código mais prático de
escrever. Embora as variáveis sejam imutáveis por padrão, você pode torná-las
mutáveis adicionando mut antes do nome da variável, como fez no Capítulo
2. Adicionar mut também
comunica intenção a futuras pessoas leitoras do código, indicando que outras
partes do programa mudarão o valor dessa variável.
Por exemplo, vamos mudar src/main.rs para o seguinte:
Nome do arquivo: src/main.rs
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
Quando executamos o programa agora, obtemos isto:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/variables`
The value of x is: 5
The value of x is: 6
Temos permissão para mudar o valor associado a x de 5 para 6 quando
mut é usado. No fim das contas, decidir usar mutabilidade ou não depende de
você e do que parecer mais claro naquela situação específica.
Declarando constantes
Assim como variáveis imutáveis, constantes são valores associados a um nome e que não podem mudar, mas há algumas diferenças entre constantes e variáveis.
Primeiro, não é permitido usar mut com constantes. Constantes não são apenas
imutáveis por padrão, elas são sempre imutáveis. Você declara constantes usando
a palavra-chave const em vez de let, e o tipo do valor deve ser anotado.
Falaremos sobre tipos e anotações de tipo na próxima seção,
“Tipos de dados”, então não se preocupe com os
detalhes agora. Só saiba que você sempre precisa anotar o tipo.
Constantes podem ser declaradas em qualquer escopo, inclusive no escopo global, o que as torna úteis para valores que muitas partes do código precisam conhecer.
A última diferença é que constantes só podem ser definidas como expressões constantes, e não como o resultado de um valor que só poderia ser calculado em tempo de execução.
Aqui está um exemplo de declaração de constante:
#![allow(unused)]
fn main() {
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
}
O nome da constante é THREE_HOURS_IN_SECONDS, e seu valor é o resultado da
multiplicação de 60, o número de segundos em um minuto, por 60, o número de
minutos em uma hora, por 3, o número de horas que queremos contar neste
programa. A convenção de nomenclatura do Rust para constantes é usar somente
maiúsculas, com underscores entre as palavras. O compilador consegue avaliar um
conjunto limitado de operações em tempo de compilação, o que nos permite
escrever esse valor de uma forma mais fácil de entender e verificar, em vez de
definir a constante diretamente como 10.800. Veja a seção do Rust Reference
sobre avaliação de constantes para mais informações sobre as
operações que podem ser usadas ao declarar constantes.
Constantes são válidas durante todo o tempo de execução do programa, dentro do escopo em que foram declaradas. Essa propriedade as torna úteis para valores do domínio da sua aplicação que múltiplas partes do programa talvez precisem conhecer, como o número máximo de pontos que qualquer jogador de um jogo pode ganhar ou a velocidade da luz.
Dar nome de constantes a valores hardcoded usados ao longo do programa é útil para transmitir o significado desse valor a futuras pessoas mantenedoras do código. Isso também ajuda a ter apenas um lugar no código que precisará ser alterado se o valor hardcoded tiver de ser atualizado no futuro.
Shadowing
Como você viu no tutorial do jogo de adivinhação no Capítulo
2, você pode declarar
uma nova variável com o mesmo nome de uma variável anterior. Rustaceanos dizem
que a primeira variável é shadowed pela segunda, o que significa que a
segunda variável é aquela que o compilador verá quando você usar aquele nome.
Na prática, a segunda variável encobre a primeira, e qualquer uso desse nome se
refere a ela até que ela própria seja shadowed ou até que o escopo termine.
Podemos fazer shadowing de uma variável repetindo o mesmo nome e usando
novamente a palavra-chave let, assim:
Nome do arquivo: src/main.rs
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
Este programa primeiro associa x ao valor 5. Em seguida, cria uma nova
variável x repetindo let x =, pegando o valor original e somando 1, de
modo que x passa a valer 6. Depois, dentro de um escopo interno criado com
chaves, a terceira instrução let também faz shadowing de x e cria uma nova
variável, multiplicando o valor anterior por 2, o que faz x valer 12.
Quando esse escopo termina, o shadowing interno acaba, e x volta a valer
6. Quando executamos esse programa, ele produz a seguinte saída:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6
Shadowing é diferente de marcar uma variável com mut, porque receberemos um
erro em tempo de compilação se, por engano, tentarmos reatribuir um valor a
essa variável sem usar a palavra-chave let. Ao usar let, podemos realizar
algumas transformações sobre um valor, mas fazer com que a variável seja
imutável depois que essas transformações terminarem.
A outra diferença entre mut e shadowing é que, como estamos efetivamente
criando uma nova variável quando usamos let de novo, podemos mudar o tipo do
valor e ainda reutilizar o mesmo nome. Por exemplo, suponha que nosso programa
peça a uma pessoa usuária que informe quantos espaços quer entre certos textos,
digitando caracteres de espaço, e depois queiramos armazenar essa entrada como
um número:
fn main() {
let spaces = " ";
let spaces = spaces.len();
}
A primeira variável spaces é do tipo string, e a segunda variável spaces é
do tipo numérico. Shadowing nos poupa de precisar inventar nomes diferentes,
como spaces_str e spaces_num; em vez disso, podemos reutilizar o nome mais
simples spaces. No entanto, se tentarmos usar mut para isso, como mostrado
aqui, receberemos um erro em tempo de compilação:
fn main() {
let mut spaces = " ";
spaces = spaces.len();
}
O erro diz que não temos permissão para mudar o tipo de uma variável:
$ cargo run
Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
--> src/main.rs:3:14
|
2 | let mut spaces = " ";
| ----- expected due to this value
3 | spaces = spaces.len();
| ^^^^^^^^^^^^ expected `&str`, found `usize`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` (bin "variables") due to 1 previous error
Agora que exploramos como as variáveis funcionam, vamos ver mais tipos de dados que elas podem assumir.