Usando variáveis e constantes em programas Rust

Usando variáveis e constantes em programas Rust

Siga em frente com seu aprendizado de Rust e familiarize-se com as variáveis e constantes dos programas Rust.

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 e u32 para inteiros de 32 bits assinados e não assinados, respectivamente
  • Tipo de ponto flutuante: números de ponto flutuante de f32 e f64, 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ê

  1. Entenda o design de Rust executando tarefas "normais", que na verdade são uma das principais causas de problemas relacionados à memória
  2. 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.

Comandos para usar no terminal

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.

Comandos para usar no terminal

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 bindingbis 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:

  1. Um nome constante deve estar em SCREAMING_SNAKE_CASE. Todos os caracteres maiúsculos e palavras separados por uma minúscula.
  2. 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.

Via itsfoss.com. Você pode conferir o post original em inglês:

Rust Basics Series #2: Using Variables and Constants in Rust Programs

Última atualização deste artigo: 1 de january de 2024