Capítulo 02 10 min de leitura

Tipos e bindings

Em Python ou JavaScript, x = 7 simplesmente funciona: a linguagem descobre em tempo de execução que x é um número. O Glide é diferente. Todo valor tem um tipo que o compilador conhece antes do programa rodar, e você geralmente escreve o tipo ao lado do nome. A recompensa por ser explícito é que o compilador consegue pegar uma enorme classe de bugs — passar uma string onde você queria um número, ler um campo que não existe — antes de você colocar o código em produção.

Bindings: let, mut, const

A coisa mais simples que você pode fazer é vincular um valor a um nome:

let x: i32 = 42;

Leia isso como "que exista uma variável chamada x do tipo i32 com o valor 42." i32 é abreviação de "inteiro de 32 bits com sinal" — mais sobre larguras em um momento.

Por padrão, bindings com let são imutáveis: uma vez que você atribuiu o valor, ele não pode mudar.

let x: i32 = 42;
x = 7;        // ✗ erro: `x` não é mutável

Se você precisar mesmo mudar o valor depois, adicione mut:

let mut count: i32 = 0;
count = count + 1;   // ✓ count é mutável
count = count + 1;

const é para valores fixos em tempo de compilação — conhecidos antes mesmo de o programa começar a rodar:

const MAX_RETRIES: i32 = 5;
const GREETING: string = "hello";

O compilador embute esses valores no binário. Eles não podem depender de entrada do usuário, conteúdo de arquivos ou qualquer outra coisa que só é conhecida em tempo de execução.

Inferência de tipos

Você pode omitir o tipo quando o lado direito deixa isso óbvio:

let n = 42;          // inferido como i32
let pi = 3.14;       // inferido como f64
let name = "glide";  // inferido como string

Mas nas assinaturas de funções (parâmetros e tipo de retorno), os tipos são obrigatórios. O raciocínio é: assinaturas são documentação; surpresas não ajudam. Você verá isso no próximo capítulo.

Inteiros, especificados pela largura

O Glide não oferece um int genérico. Você escolhe uma largura que corresponda ao que o valor vai armazenar:

let a: i8   = -100;             // -128 .. 127
let b: i16  = 30_000;           // ±32K
let c: i32  = 2_000_000_000;    // ±2 bilhões — o padrão habitual
let d: i64  = 9_000_000_000;    // ±9 quintilhões
let e: i128 = 170_141_183_460_469_231_731_687_303_715_884_105_727;

let p: u8   = 255;              // sem sinal: 0 .. 255 (sem negativos)
let q: u16  = 65535;
let r: u32  = 4_000_000_000;
let s: u64  = 18_000_000_000_000_000_000;

O i significa com sinal (pode ser negativo), o u significa sem sinal (apenas zero e acima). O número é a largura em bits — um número maior = intervalo maior = mais memória usada por valor.

Sublinhados em literais numéricos são apenas um recurso visual — 1_000_000 é o mesmo que 1000000. Literais podem ser decimais, hexadecimais ou binários:

let mask: u32  = 0xFFFF_0000;      // hex
let bits: u8   = 0b1010_1100;      // binário
let count: i32 = 1_000_000;        // decimal simples

Não há conversões implícitas de redução de largura. Para ir de i64 para i32, você escreve a conversão explicitamente com as:

let big: i64 = 100;
let small: i32 = big as i32;

Floats

Duas larguras: f32 (cerca de 7 dígitos decimais de precisão) e f64 (cerca de 15). f64 é o padrão para literais decimais sem anotação — é o que você vai querer quase sempre. (float é um alias que você encontrará em alguns códigos; equivale a f64.)

let g: f32 = 1.5;
let h: f64 = 2.718281828;

O Glide não converte inteiros para floats automaticamente. let x: f64 = 1; é um erro — escreva 1.0 ou 1 as f64.

Booleanos

bool armazena true ou false. É um tipo de verdade, distinto de inteiros — if 1 { ... } é rejeitado pelo compilador.

let ready: bool = true;
let done:  bool = false;

if ready && !done {
    println!("go");
}

Os operadores são && (e), || (ou), ! (não).

Strings

string é uma sequência de bytes UTF-8 — o tipo de texto. Literais entre aspas duplas com as sequências de escape usuais (\n, \t, \", \\):

let s: string = "hello, glide";
println!(s);
println!("length:",  s.len());
println!("upper:",   s.to_upper());
println!("first 5:", s.substring(0, 5));

Dentro de um literal, ${expr} interpola uma expressão — o mesmo padrão dos template literals do JavaScript:

let n: i32 = 7;
let msg: string = "got ${n} results";

Alguns métodos de string que você vai usar bastante: .len(), .eq(other), .contains(sub), .starts_with(p), .ends_with(s), .trim(), .to_upper(), .to_lower(), .split(sep), .replace(find, repl), .substring(start, end), .index_of(sub), .concat(other).

Ponteiros

É aqui que o Glide começa a se diferenciar do Python de forma visível. Um ponteiro é um valor que armazena o endereço de outro valor. O tipo *T significa "ponteiro para um T."

let x: i32 = 42;
let p: *i32 = &x;     // & significa "obtenha o endereço de"
println!(*p);          // * significa "siga o ponteiro"  → 42

Você não vai escrever &x e *p com frequência para escalares. Onde ponteiros aparecem em todo lugar é em objetos alocados no heap — valores que vivem no heap porque crescem em tempo de execução:

let v: *Vector<i32> = Vector::new();
v.push(1);
v.push(2);

Aqui, Vector::new() aloca um novo vector no heap e devolve um ponteiro para ele. Você pode chamar métodos diretamente pelo ponteiro (v.push(...)); o Glide descobre sozinho quando desreferenciar.

Modelo de memória é o capítulo inteiro dedicado a explicar como isso funciona sem precisar de um coletor de lixo. Por ora: qualquer valor criado com Type::new() vive no heap, e o Glide rastreia quem tem a posse dele e insere a limpeza correta automaticamente.

Structs

Um struct é um conjunto fixo de campos nomeados empacotados juntos. Você define o tipo uma vez e constrói valores com a sintaxe de literal de struct:

struct Point {
    x: f64,
    y: f64,
}

fn main() -> i32 {
    let origin: Point = Point { x: 0.0, y: 0.0 };
    let p:      Point = Point { x: 3.0, y: 4.0 };
    let dist:   f64   = (p.x * p.x + p.y * p.y);
    println!("distance² from origin:", dist);
    return 0;
}

Acesse campos com .campo. Se você já usou dataclasses do Python, structs parecem algo familiar — exceto que cada campo tem um tipo, e você não pode adicionar um campo que não estava na declaração.

Vectors

Vector<T> é uma lista expansível de valores, todos do tipo T. Como uma lista do Python, mas com todos os elementos do mesmo tipo. Ele vive no heap e tem a posse do seu conteúdo:

let v: *Vector<i32> = Vector::new();
v.push(1);
v.push(2);
v.push(3);
println!("len:", v.len());
println!("first:", v.get(0));

for x in v {
    println!(x);
}

A macro push_all! insere vários valores em uma linha — útil para popular um vector rapidamente:

let primes: *Vector<i32> = Vector::new();
primes.push_all!(2, 3, 5, 7, 11);

HashMaps

HashMap<V> é uma tabela hash com chaves do tipo string. Os valores podem ser de qualquer tipo V:

let h: *HashMap<i32> = HashMap::new();
h.insert("alice", 30);
h.insert("bob",   25);

if h.contains("alice") {
    println!("alice is", h.get("alice"));
}

Para chaves que não são strings, você recorre às variantes tipadas em stdlib::hashmap — voltaremos a isso quando precisarmos.

Um primeiro olhar em ?T e !T

Mais dois tipos que você vai ver em todo lugar no código Glide, abordados em profundidade em Erros como valores:

  • `?T` — "talvez um T". Ou some(value) ou none(). Use quando uma função pode não ter nada para retornar.
  • `!T` — "um T ou uma mensagem de erro". Ou ok(value) ou err("message"). Use quando uma operação pode falhar.
fn first_byte(s: string) -> ?i32 {
    if s.len() == 0 { return none(); }
    return some(s.at(0).to_int());
}

fn parse(s: string) -> !i32 {
    if s.eq("") { return err("empty input"); }
    return ok(42);
}

Para onde ir agora

Os valores estão catalogados. A seguir você vai movimentá-los com Funções e fluxo de controle.