Executando verificação de segurança...
9

Variáveis e tipos em Rust.

Mais um post do meu progresso em Rust.
Capa do post
Em Rust existe dois tipos de variáveis e por padrão são imutáveis, isso para manter o seu código mais seguro e de fácil concorrência. Podemos declarar uma variável com as palavras reservadas let ou const⁣.⁣Se você veio do JavaScript sabe muito bem que podemos reatribuir valores em variáveis do tipo let no Rust é bem parecido, porém precisamos falar que essa let é do tipo mut dai conseguimos reatribuir valores nessa variável. Bora entender isso, na prática.

Podemos criar um mini projeto ou simplesmente criar um arquivo com a extensão rs. Criei um projeto usando o cargo, basta digitar no terminal:

cargo new variaveis
Pronto projeto criado, vamos alterar o arquivo main.rs para vermos as variáveis funcionando:

fn main() {
    let a = 1;

    println!("O valor de a é {}", a);

    a = 2;

    println!("O valor de a agora é {}", a);
}

Com esse código teremos o seguinte erro no console:
console)

Por isso tenho curto Rust, olha a mensagem que ele devolve, basicamente ele diz que o já está sendo usado, para fazer o binding de um novo valor podemos usar a palavra reservada mut para que isso seja possível, além disse em vermelho no console, temos a mensagem que a variável a é imutável. Então o Rust facilita muito para podermos resolver o problema no nosso código, bora lá:

fn main() {
    let mut a = 1;

    println!("O valor de a é {}", a);

    a = 2;

    println!("O valor de a agora é {}", a);
}

Agora nosso programa funcionará e no console recebemos o seguinte:
mensagem de sucesso

Diferenças entre const e let

Uma const não pode receber a palavra mut para ser mutável, de fato não conseguimos mudar o seu valor. Precisamos “tipar” uma const para ela funcionar corretamente, elas podem ser declaradas em qualquer escopo, incluindo escopo global, então se você precisa de uma regra que vai ser usado em todo seu projeto, pode-se usar const para isso, por último não podemos receber valores de uma chamada em uma constante, ou qualquer outro valor que só temos o resultado após a execução do programa. Vamos para um exemplo:

fn main() {
    const MAX_VALUE: u32 = 100_000;
}
Por convenção Rust usa letra maiúsculas separadas por um sublinhado logo em seguida. Podemos tentar burlar código acima de duas maneiras para saber se realmente podemos tentar modificar uma constante ou deixar ela sem tipo:

fn main() {
    const mut MAX_VALUE: u32 = 100_000;
    println!("O valor máximo é {}", MAX_VALUE);
}

// recebemos o erro: const globals cannot be mutable

ou

fn main() {
    const mut MAX_VALUE = 100_000;
    println!("O valor máximo é {}", MAX_VALUE);
}

// missing type for `const` item

Shadowing

Esse é um conceito que ainda estou tentando internalizar, mas basicamente falando em Rust, podemos ter duas ou mais variáveis com o mesmo nome, isso causa o efeito de “sombra”, pois o primeiro valor é “sombreado” pelo segundo valor, bora para um exemplo:

fn main() {
    let x = 1;
    println!("o valor de x = {}", x); // valor de x 1
    let x = x + 1;
    println!("o valor de x = {}", x); // valor de x 2
    let x = x + 2;
    println!("o valor de x = {}", x); // valor de x 4
    let x = "shadowing";
    println!("o valor de x = {}", x); // valor de x shadowing
}

Isso é bem legal, mas existe uma diferença entre reutilizar a variável no nosso caso X do que tornar a variável mutável, pois com variáveis mutáveis, você pode mudar o valor dela, mas não o tipo:

fn main() {
    let mut x = 1;

    println!("o valor de x: {}", x);

    x = "shadowing";

    println!("o valor de x: {}", x);
}

Recebemos o erro:
Erro no console

Tipos de dados em Rust

Rust é uma linguagem estaticamente tipada, o que significa que precisa saber todos os tipos de variáveis em tempo de compilação, o compilador Rust é inteligente para saber qual é o tipo de variável X, pois podemos usar let sem dizer se é do tipo string ou number por exemplo, aqui em Rust também podemos usar a API de parse para fazer uma mudança dessa se for o caso.

Inteiro

É um número sem parte fracionária, podemos usar de duas maneiras, declarando que ele é um i8 ou u8 o quê isso quer dizer ? que no primeiro caso temos um inteiro com sinal de 8 bits e o segundo caso um número inteiro sem sinal sem sinal, Rust fornece uma tabela de inteiros que podemos usar:
tabela de valores

Podemos usar por exemplo i8 em números negativos, se precisarmos ter algum cálculo com algum número negativo. Além disso, os tipos isize e usize dependem do computador em que seu programa está rodando: 64 bits se estiver em uma arquitetura de 64-bit e 32 bits se sua arquitetura for 32-bit. Se você não souber qual tipo usar para uma variável do tipo inteiro a própria documentação indica i32 esse é tipo mais rápido geralmente.

Ponto flutuante

São números com fração, no caso ele só possui dois tamanhos f32 e f64 32 e 64 bits respectivamente.

Tipo boolean

Para verdadeiro ou falso, se quisermos podemos deixar explicito o que aquela variável irá receber um boolean:

fn main() {
    let t = true;

    let f: bool = false;
}

Tipo caractere

Recebe apenas uma letra ou o unicode de um caractere, para usar precisamos declarar com aspas simples.

Tupla

É um valor complexo, geralmente serve para agrupar mais de um valor dentro de uma variável. Podemos criar uma tupla escrevendo uma lista de valores separados por vírgula dentro de parênteses. Cada posição da tupla tem um tipo e os tipos dos elementos da tupla não necessitam serem iguais.

fn main() {
    let tuple: (i32, f64, u8) = (-1, 6.4, 1);

    println!("{:?}", tuple);
}

Para quem está acostumado com JavaScript, podemos fazer a desestruturação de uma tupla e nomear elemento, por elemento:

fn main() {
    let tuple: (-1, 6.4, 1);
    (a, b, c) = tuple

    println!("o valor de a {}", a);
}

Também podemos acessar via indices:

fn main() {
    let x: (i32, f64, u8) = (-1, 6.4, 1);

    let menos_um = x.0;

    let seis_ponto_quatro = x.1;

    let um = x.2;
}

Matriz

Mais conhecido como vetor:

fn main() {
    let a = [1, 2, 3, 4, 5];
}

Bem isso foi o que eu aprendi sobre Rust nessas duas últimas semanas, minha vida está bem corrida, então não deu para ir tão profundamente nos estudos dessa linguagem maravilhosa como eu gostaria, mas vou continuar e semana que vem eu trago mais coisas sobre ela.

Minha maior fonte de aprendizagem está sendo a doc em pt-br 😍 excelente que a comunidade fez e vem fazendo, então deem uma olhada na doc. Bom é isso galera, espero que tenham gostado, lembrando que qualquer feedback é muito bem-vindo, valeu até a próxima.

Carregando publicação patrocinada...
2

Mas vc já programava antes ou é seu primeiro contato com a programação? ( se sim perdão e ignore oque eu falar a seguir ), não me leve a mal, mas conteúdos como esses são indiferentes e independente da linguagem, como dev C++, é sempre bom ver e aprender com outras linguagens mas esses conteúdos são meio "méeeh". Sugestão, no C++ temos string, string_view e c_str, cada um tem seus custos e beneficios (alocações, facilidades de uso e etc), iria adorar ler um post sobre os 16 tipos de String que existem no Rust, seria bem mais interessante.

1

Opa fala ai brother, sim eu programo em JS há alguns anos, na verdade eu quero mostrar com esses posts, a minha evolução com o Rust, quero mostrar o passo a passo do que eu estou aprendendo, sei que esse conteúdo acaba sendo meio fraco, mas a ideia é para os posts futuros é evoluir e indo cada vez mais profundo em cada um deles.

1

Muito bom cara, parabéns. Vou te dar uma dica, atualmente desenvolvo e gerencio um time que desenvolve um produto em C++. Eu recomendo a todos os integrantes a evitarem usar as autodeclarações, no caso do Rust o let, eles tem sua utilidade com certeza. Porém, eu vejo a maior vantagem de linguagens como Go, C++, C, Rust e Java é que a gente sabe qual o tipo de variável que estamos lidando, então fazer as declarações com os tipos já corretos facilita debugging.

Eu parei de estudar Rust, mas vou acompanhar seus posts

2

Sim essa é uma vantangem enermo, eu que sou do JS que podemos fazer uma "baguncinha" é bem diferente, estou estudando aos poucos, pois estou com altas responsabilidades agora, mas até o final do ano quero ter uma boa autonomia na linguagem, sim vou continur estudando e postando aqui. Obrigado por ajudar o post com esse feedback

1

Eu não sei se essa é uma recomendação muito razoável. C++ é uma coisa específica, o 'auto' foi um afterthought da linguagem e, apesar de ser muito utilizado (e eu diria recomendável em muitos casos), não se integra tão naturalmente. Rust por outro lado foi pensado do início pra utilizar os maiores desenvolvimentos das teorias de tipos e de inferência, é uma linguagem feita pra aproveitar ao máximo a solidez que tipagem forte permite e ao mesmo tempo a flexibilidade que utilizar a inferência de tipos permite. Se você pegar um C# ou C++ por exemplo, o tipo geralmente precede a variável, e conceitos como 'var' ou 'auto' são construídos em cima disso, em Rust o que precede é a keyword 'let' e o tipo aparece como um opcional através do ':'.

Além de que Rust possui tipos extremamente verbosos e complicados por conta de suas features de segurança e as técnicas avançadas empregadas no desenvolvimento e, justamente, a inferência permite que você não precise tipar a todo momento eles (o sistema de tipos é avançado ao ponto de que é literalmente retroativo, é algo comum ver um código que declara por exemplo um vec![] e que o tipo desse Vec é determinado pelo uso, posteriormente; o borrow checker também participa disso e códigos que seriam inválidos sendo feitos de uma determinada forma X, podem se tornar válidos se escritos de uma forma Y que garanta que as regras mais estritas não estejam sendo violadas). O próprio conceito de erros como valores por exemplo, imagine ter de escrever manualmente algo como:

let result: Result<Result<&'a Foo, std::io::Error>, tokio::time::Elapsed> = ...; 

Só pra poder passar um valor ou iterar, é insano pensar que isso seria recomendado, eu diria que torna o código muito mais confuso do que autodeclarações jamais fariam nesse caso.

Rust faz com que os tipos das variáveis sejam suficientemente explicitos sem precisar ser explicito a todo momento, ele requer que você tipe fortemente todos os parâmetros e retornos de todas as suas funções, e todo pattern matching faz uso pesado e representativo de seus tipos interiores. Ele também incentiva o uso de type hintings nos editores (o que permite ter um nível a mais de previsibilidade enquanto se está escrevendo mesmo sem escrever explicitamente os tipos). Então você geralmente vai ser capaz de saber o que X é, se X for relevante, na medida em que é utilizado.

Em debugging isso não faz praticamente nenhuma diferença, a esmagadora maioria dos debuggers vão fazer uso pesado de pretty printing e isso só importa na medida em que o compilador sabe qual o tipo, você só precisa saber dos valores (e supondo que está debugando em um editor de código, vai ter acesso a todos os tipos).

E tem uma outra vantagem clara disso que é a facilidade de refatoração. Quanto menos seu código depende de tipos estritamente, mais ele vai depender da "injeção" dos tipos feita de forma externa pelo caller, e menos você precisará se preocupar em modificar individualmente os tipos em cada única variável e cada única permutação dessa mesma variável (e.g. as funções de mapeamento monádicas). O uso de uma variável ditará as regras que ela precisa seguir na medida dos tipos aos quais ela obedece (conjunção de struct + traits), se uma função não for aplicável no contexto Y ela emitirá um erro, se um caso em um match não for coberto (devido à modificação do tipo) ele emitirá um erro, e assim em diante.

Acho que é importante respeitar as filosofias de cada linguagem ao invés de tentar encaixar todas em uma caixa com base em percepções específicas do que você se acostumou como sendo mais razoável, em linguagens funcionais como OCaml por exemplo você raramente sequer usaria tipos explícitos (mesmo nas declarações das funções, e isso considerando que é uma linguagem com tipagem extremamente forte), Rust tem seus momentos onde faz mais sentido usar a inferência, e outros onde não faz, e linguagens como C++, C# ou Java tem outros (Java por exemplo não tinha suporte a 'var' até a versão 10, enquanto que Kotlin [que roda também na JVM] tinha desde seu começo suporte nativo e bastante sólido à inferência de tipos, e seu uso não é desrecomendado de modo algum, é um dos motivos pra eu preferir pessoalmente Kotlin à Java).

Enfim, esses são meus 2¢.

1
1
1
1