DEV Community

Cover image for Meia hora aprendendo Rust - Parte 1
Rodolfo Ghiggi
Rodolfo Ghiggi

Posted on

Meia hora aprendendo Rust - Parte 1

Esta é uma tradução livre do post escrito por Amos que você pode ler aqui. Como o artigo é bem extenso vou quebrar ele em pelo menos duas partes, esta é a parte 1.

A tradução foi autorizada por Amos através de mensagem direta no Twitter.

Para adquirir fluência em uma linguagem de programação é preciso ler muito. Mas como ler muito se ainda não sabemos o que aquele linguagem significa?

Neste artigo, em vez de focar em um ou dois conceitos, tentarei analisar o maior número possível de trechos de Rust e explicar o que significam as palavras-chave e os símbolos que eles contêm.

Pronto? Vamos lá!

Palavra chave let

let é utilizado pra criar uma variável:

let x; // declara "x"
x = 42; // define o valor 42 para "x"
Enter fullscreen mode Exit fullscreen mode

Isso também pode ser escrito em apenas uma linha:

let x = 42;
Enter fullscreen mode Exit fullscreen mode

Você pode definir explicitamente o tipo da variável utilizando : , que é uma anotação de tipo:

let x: i32; // `i32` é um valor inteiro de 32 bits com sinal
x = 42;


// também há i8, i16, i32, i64, i128
//    além disso há u8, u16, u32, u64, u128 para valores sem sinal
Enter fullscreen mode Exit fullscreen mode

Isso também pode ser escrito em apenas uma linha:

let x: i32 = 42;
Enter fullscreen mode Exit fullscreen mode

O compilador de Rust impede o uso de variáveis não inicializadas.

let x;
foobar(x); // error: borrow of possibly-uninitialized variable: `x`
x = 42;
Enter fullscreen mode Exit fullscreen mode

E o fato de ele fazer isso é muito bom:

let x;
x = 42;
foobar(x); // a variável `x` pode ser utilizada aqui
Enter fullscreen mode Exit fullscreen mode

O sublinhado(underline ou underscore) _ é um nome especial, ou melhor uma falta de nome, basicamente significa jogar algo fora:

// isso não faz nada pois 42 é uma constante
let _ = 42;

// isso chama a função `get_thing` mas joga o seu resultado fora
let _ = get_thing();
Enter fullscreen mode Exit fullscreen mode

Nomes que começam com sublinhado são nomes válidos, mas o compilador não alerta sobre a não utilização dessas variáveis:

// nós podemos usar `_x` eventualmente, quando queremos nos 
// livrar do aviso do compilador sobre variável não utilizada.
let _x = 42;
Enter fullscreen mode Exit fullscreen mode

É possível inicializar variáveis separadamente utilizado o mesmo nome — você pode sobrescrever uma variável:

let x = 13;
let x = x + 3;
// usando `x` depois desta linha, refere-se apenas ao segundo `x`,
// o primeiro `x` não existe mais.
Enter fullscreen mode Exit fullscreen mode

Tuplas

O Rust possui tuplas, que você pode considerar como “coleções de valores fixos de tipos diferentes”.

let pair = ('a', 17);
pair.0; // isso é'a'
pair.1; // isso é 17
Enter fullscreen mode Exit fullscreen mode

Se você realmente precisar, pode definir os tipos dos valores da tupla:

let pair: (char, i32) = ('a', 17);
Enter fullscreen mode Exit fullscreen mode

Tuplas podem ser desconstruídas, ou seja, os seus valores podem ser atribuídos a variáveis individuais:

let (some_char, some_int) = ('a', 17);
// agora, `some_char` é 'a', e `some_int` é17
Enter fullscreen mode Exit fullscreen mode

Isso é bem útil quando uma função retorna uma tupla:

let (left, right) = slice.split_at(middle);
Enter fullscreen mode Exit fullscreen mode

Obviamente, ao desconstuir uma tulpa pode-se descartar parte dela utilizando _ :

let (_, right) = slice.split_at(middle);
Enter fullscreen mode Exit fullscreen mode

Ponto e vírgula

O ponto e vírgula ; define o fim da instrução:

let x = 3;
let y = 5;
let z = y + x;
Enter fullscreen mode Exit fullscreen mode

Isso significa que uma instrução pode conter várias linhas:

let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
    .iter()
    .map(|x| x + 3)
    .fold(0, |x, y| x + y);
Enter fullscreen mode Exit fullscreen mode

Omitir o ponto e vírgula no final da função é o mesmo que retornar:

fn fair_dice_roll() -> i32 {
    return 4;
}

fn fair_dice_roll() -> i32 {
    4
}
Enter fullscreen mode Exit fullscreen mode

Funções

fn declara uma função:

fn greet() {
    println!("Olá!");
}
Enter fullscreen mode Exit fullscreen mode

E esta função retorna um inteiro com sinal, a seta -> indica o tipo de retorno:

fn fair_dice_roll() -> i32 {
    4
}
Enter fullscreen mode Exit fullscreen mode

Blocos {}

O par de chaves é utilizado para declarar um bloco que possui seu próprio escopo:

// Isso imprime "in", e depois "out"
fn main() {
    let x = "out";
    {
        // este é outro `x`
        let x = "in";
        println!(x);
    }
    println!(x);
}
Enter fullscreen mode Exit fullscreen mode

Blocos também são expressões, o que significa que eles avaliam um valor:

// isso:
let x = 42;

// é equivalente a:
let x = { 42 };
Enter fullscreen mode Exit fullscreen mode

Dentro de um bloco, podemos ter várias instruções:

let x = {
    let y = 1; // primeira instrução
    let z = 2; // segunda instrução
    y + z // isso é o que o bloco avaliará e colocará o resultado em `x`
};
Enter fullscreen mode Exit fullscreen mode

Condicionais

Condicionais if também são expressões:

fn fair_dice_roll() -> i32 {
    if feeling_lucky {
        6
    } else {
        4
    }
}
Enter fullscreen mode Exit fullscreen mode

Um match também é uma expressão:

fn fair_dice_roll() -> i32 {
    match feeling_lucky {
        true => 6,
        false => 4,
    }
}
Enter fullscreen mode Exit fullscreen mode

O match é exaustivo: pelo menos um dos cados precisa bater:

fn print_number(n: Number) {
    match n {
        Number { value: 1, .. } => println!("One"),
        Number { value: 2, .. } => println!("Two"),
        Number { value, .. } => println!("{}", value),
        // Se este último caso não existir você receberá um erro do compilador
    }
}
Enter fullscreen mode Exit fullscreen mode

Se isso for ruim para você, pode-se usar o _ para “pegar todos” os padrões:

fn print_number(n: Number) {
    match n.value {
        1 => println!("One"),
        2 => println!("Two"),
        _ => println!("{}", n.value),
    }
}
Enter fullscreen mode Exit fullscreen mode

Acessando valores

Ponto . normalmente é utilizado para acessar o valor de um campo:

let a = (10, 20);
a.0; // isso é 10

let amos = get_some_struct();
amos.nickname; // isso é "fasterthanlime"
Enter fullscreen mode Exit fullscreen mode

Ou para chamar uma função:

let nick = "fasterthanlime";
nick.len(); // isso é 14
Enter fullscreen mode Exit fullscreen mode

Os dois pontos duplos :: , são parecidos, mas eles acessam os namespaces (não sabia exatamente como traduzir isso, então deixei assim).

Neste exemplo, std é uma biblioteca, cmp é um módulo (~ um arquivo de código), e min é uma função:

let least = std::cmp::min(3, 8); // isso é 3 
Enter fullscreen mode Exit fullscreen mode

A diretiva use pode ser utilizada para “trazer para o escopo” nomes de outros namespaces:

use std::cmp::min;

let least = min(7, 1); // isso é 1
Enter fullscreen mode Exit fullscreen mode

Diretiva use

Na diretiva use as chaves {} possuem outro significado: elas são “ globs”. Se quisermos importar as funções min e max , podemos fazer assim:

// isso funciona:
use std::cmp::min;
use std::cmp::max;

// isso também:
use std::cmp::{min, max};

// e isso também!
use std::{cmp::min, cmp::max}
Enter fullscreen mode Exit fullscreen mode

O curinga (*) permite importar tudo de um namespace:

// isso importa `min` e `max` e muitas outras coisas
use std::cmp::*;
Enter fullscreen mode Exit fullscreen mode

Tipos também são namespaces

Tipos também são namespaces, e os seus métodos podem ser chamados normalmente:

let x = "amos".len(); // isso é 4
let x = str::len("amos"); // isso também é 4
Enter fullscreen mode Exit fullscreen mode

str é um tipo primitivo, mas muitos tipos não primitivos, também estão no escopo por padrão:

// `Vec` é uma estrutura regular, não um tipo primitivo
let v = Vec::new();

// este é exatamente o mesmo código, mas com o caminho completo para `Vec`
let v = std::vec::Vec::new();
Enter fullscreen mode Exit fullscreen mode

Isso funciona porque o Rust insere automaticamente no início de cada módulo:

use std::prelude::v1::*;
Enter fullscreen mode Exit fullscreen mode

(Que por sua vez, reexporta muitos símbolos, como Vec, String, Option e Result).

Estruturas

Estruturas são declaradas através da palavra chave struct:

struct Vec2 {
    x: f64, // 64 bits com ponto flutuante, também conhecido como "double precision"
    y: f64,
}
Enter fullscreen mode Exit fullscreen mode

Elas podem ser inicializadas através de estruturas literais:

let v1 = Vec2 { x: 1.0, y: 3.0 };
let v2 = Vec2 { y: 2.0, x: 4.0 };
// a ordem não importa, apenas os nomes
Enter fullscreen mode Exit fullscreen mode

Existe um atalho para inicializar o resto dos campos a partir de outra estrutura:

let v3 = Vec2 {
    x: 14.0,
    ..v2
};
Enter fullscreen mode Exit fullscreen mode

Isso é chamado de “sintaxe de atualização de estrutura”, só pode acontecer na última posição e não pode ser seguido por uma vírgula.

Observe que podemos inicializar todos os campos:

let v4 = Vec2 { ..v3 };
Enter fullscreen mode Exit fullscreen mode

Estruturas, assim como as tuplas, também podem ser desconstruídas:

Assim como isto é um padrão válido:

let (left, right) = slice.split_at(middle);
Enter fullscreen mode Exit fullscreen mode

Isso também:

let v = Vec2 { x: 3.0, y: 6.0 };
let Vec2 { x, y } = v;
// `x` agora é 3.0, `y` é `6.0`
Enter fullscreen mode Exit fullscreen mode

E isso também:

let Vec2 { x, .. } = v;
// isso descarta o valor de `v.y`
Enter fullscreen mode Exit fullscreen mode

Você pode declarar funções nas suas estrutuas:

struct Number {
    odd: bool,
    value: i32,
}

impl Number {
    fn is_strictly_positive(self) -> bool {
        self.value > 0
    }
}
Enter fullscreen mode Exit fullscreen mode

E utilizá-las como de costume:

fn main() {
    let minus_two = Number {
        odd: false,
        value: -2,
    };
    println!("positive? {}", minus_two.is_strictly_positive());
    // isso imprime "positive? false"
}
Enter fullscreen mode Exit fullscreen mode

let em condicionais

O let também pode ser usado em condicionais if:

struct Number {
    odd: bool,
    value: i32,
}

fn main() {
    let one = Number { odd: true, value: 1 };
    let two = Number { odd: false, value: 2 };
    print_number(one);
    print_number(two);
}

fn print_number(n: Number) {
    if let Number { odd: true, value } = n {
        println!("Odd number: {}", value);
    } else if let Number { odd: false, value } = n {
        println!("Even number: {}", value);
    }
}

// isso imprime:
// Odd number: 1
// Even number: 2
Enter fullscreen mode Exit fullscreen mode

Os casos do match também são padrões como if let:

fn print_number(n: Number) {
    match n {
        Number { odd: true, value } => println!("Odd number: {}", value),
        Number { odd: false, value } => println!("Even number: {}", value),
    }
}
// isso imprime o mesmo de antes
Enter fullscreen mode Exit fullscreen mode

Variáveis são imutáveis por padrão

Variáveis são imutáveis por padrão, então você não pode alterá-las:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n.odd = false; // error: cannot assign to `n.odd`,
                   // as `n` is not declared to be mutable
}
Enter fullscreen mode Exit fullscreen mode

E elas também não podem receber valores assim:

fn main() {
    let n = Number {
        odd: true,
        value: 17,
    };
    n = Number {
        odd: false,
        value: 22,
    }; // error: cannot assign twice to immutable variable `n`
}
Enter fullscreen mode Exit fullscreen mode

A palavra chave mut declara a variável como mútavel:

fn main() {
    let mut n = Number {
        odd: true,
        value: 17,
    }
    n.value = 19; // tudo certo
}
Enter fullscreen mode Exit fullscreen mode

Referências

Top comments (0)