Ranges em Rust: .., ..=, e RangeFull

Guia completo de ranges em Rust: Range, RangeInclusive, RangeFrom, RangeTo, RangeFull, iteração, slicing, contains e uso em for loops. Exemplos práticos.

Os ranges (faixas) em Rust são tipos que representam intervalos de valores. Eles são usados extensivamente em for loops, fatiamento de slices, verificação de pertinência e consultas em coleções ordenadas. A sintaxe com .. e ..= é compacta e expressiva, sendo um dos recursos mais utilizados da linguagem.

Tipos de Range

Rust possui seis tipos de range, todos no módulo std::ops:

SintaxeTipoIntervalo MatemáticoExemplo
a..bRange<T>[a, b)0..10 = 0 a 9
a..=bRangeInclusive<T>[a, b]0..=10 = 0 a 10
a..RangeFrom<T>[a, +inf)5.. = 5, 6, 7, …
..bRangeTo<T>(-inf, b)..5 = até 4
..=bRangeToInclusive<T>(-inf, b]..=5 = até 5
..RangeFull(-inf, +inf).. = tudo

Criando e Usando Ranges

use std::ops::{Range, RangeInclusive, RangeFrom, RangeTo, RangeFull};

fn main() {
    // Range exclusivo: [0, 5)
    let r1: Range<i32> = 0..5;
    println!("0..5 = {:?}", r1);

    // Range inclusivo: [0, 5]
    let r2: RangeInclusive<i32> = 0..=5;
    println!("0..=5 = {:?}", r2);

    // Range aberto à direita: [3, +inf)
    let r3: RangeFrom<i32> = 3..;
    println!("3.. = {:?}", r3);

    // Range aberto à esquerda: (-inf, 5)
    let r4: RangeTo<i32> = ..5;
    println!("..5 = {:?}", r4);

    // Range full (todos os elementos)
    let r5: RangeFull = ..;

    // Ranges com outros tipos
    let letras = 'a'..='z';
    let floats = 0.0..1.0;

    println!("Letras: {:?}", letras);
    println!("Floats: {:?}", floats);
}

Tabela de Operações

OperaçãoFunciona ComDescrição
for x in rangeRange, RangeInclusive, RangeFromIteração
slice[range]Todos os tiposFatiamento
range.contains(&v)Todos os tiposVerificação de pertinência
range.is_empty()Range, RangeInclusiveVerifica se o intervalo é vazio
range.count()Range, RangeInclusiveNúmero de elementos (consome)
range.sum()Range, RangeInclusiveSoma dos elementos (consome)
range.collect()Range, RangeInclusiveColeta em coleção
range.rev()Range, RangeInclusiveIteração reversa
range.step_by(n)Range, RangeInclusivePasso customizado
range.zip(other)Range, RangeInclusiveCombina com outro iterador
range.enumerate()Range, RangeInclusiveAdiciona índice
range.map(f)Range, RangeInclusiveTransforma elementos
range.filter(f)Range, RangeInclusiveFiltra elementos

Exemplos Práticos

1. Ranges em For Loops

fn main() {
    // Range exclusivo: 0, 1, 2, 3, 4
    print!("0..5:   ");
    for i in 0..5 {
        print!("{} ", i);
    }
    println!();

    // Range inclusivo: 1, 2, 3, 4, 5
    print!("1..=5:  ");
    for i in 1..=5 {
        print!("{} ", i);
    }
    println!();

    // Range reverso
    print!("Rev:    ");
    for i in (1..=5).rev() {
        print!("{} ", i);
    }
    println!();

    // Range com passo (step_by)
    print!("Step 2: ");
    for i in (0..10).step_by(2) {
        print!("{} ", i);
    }
    println!();

    // Range de caracteres
    print!("a..=f:  ");
    for c in 'a'..='f' {
        print!("{} ", c);
    }
    println!();

    // Contagem regressiva (countdown)
    print!("10..1:  ");
    for i in (1..=10).rev() {
        print!("{} ", i);
    }
    println!("Lançar!");

    // Usando range com variáveis
    let inicio = 5;
    let fim = 10;
    let soma: i32 = (inicio..=fim).sum();
    println!("\nSoma de {}..={}: {}", inicio, fim, soma); // 45
}

2. Ranges para Fatiamento de Slices e Strings

fn main() {
    let dados = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];

    // Fatias com diferentes tipos de range
    let primeiros = &dados[..3];       // [10, 20, 30]
    let meios = &dados[3..7];          // [40, 50, 60, 70]
    let ultimos = &dados[7..];         // [80, 90, 100]
    let ate_5 = &dados[..=4];          // [10, 20, 30, 40, 50]
    let tudo = &dados[..];             // todos os elementos

    println!("Primeiros 3: {:?}", primeiros);
    println!("Do 4o ao 7o: {:?}", meios);
    println!("Últimos 3: {:?}", ultimos);
    println!("Até 5o (incl.): {:?}", ate_5);
    println!("Tudo: {:?}", tudo);

    // Fatiamento de strings
    let texto = "Olá, Mundo!";
    let ola = &texto[..5]; // cuidado: índices em bytes, não caracteres!
    let mundo = &texto[6..11];
    println!("{} -> {}", ola, mundo);

    // Fatiamento de Vec
    let mut vetor = vec![1, 2, 3, 4, 5];
    let fatia = &vetor[1..4];
    println!("Fatia do Vec: {:?}", fatia); // [2, 3, 4]

    // Modificar fatia
    for elem in &mut vetor[2..] {
        *elem *= 10;
    }
    println!("Vetor modificado: {:?}", vetor); // [1, 2, 30, 40, 50]
}

3. contains — Verificação de Pertinência

fn main() {
    // contains funciona com todos os tipos de range
    let faixa = 10..50;
    println!("25 em 10..50? {}", faixa.contains(&25));  // true
    println!("50 em 10..50? {}", faixa.contains(&50));  // false (exclusivo!)
    println!("50 em 10..=50? {}", (10..=50).contains(&50)); // true (inclusivo!)

    // Ranges abertos
    println!("5 em ..10? {}", (..10).contains(&5));   // true
    println!("15 em 10..? {}", (10..).contains(&15));  // true

    // Com caracteres
    let minusculas = 'a'..='z';
    let maiusculas = 'A'..='Z';
    let digitos = '0'..='9';

    let c = 'R';
    if maiusculas.contains(&c) {
        println!("'{}' é maiúscula", c);
    }

    // Classificar caracteres
    for ch in "Rust 2026!".chars() {
        let tipo = if ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) {
            "letra"
        } else if digitos.contains(&ch) {
            "dígito"
        } else {
            "outro"
        };
        println!("  '{}' -> {}", ch, tipo);
    }
}

4. Ranges com Iteradores

fn main() {
    // collect para criar coleções
    let numeros: Vec<i32> = (1..=10).collect();
    println!("1..=10: {:?}", numeros);

    // map + collect
    let quadrados: Vec<i32> = (1..=5).map(|x| x * x).collect();
    println!("Quadrados: {:?}", quadrados); // [1, 4, 9, 16, 25]

    // filter + collect
    let pares: Vec<i32> = (1..=20).filter(|x| x % 2 == 0).collect();
    println!("Pares: {:?}", pares);

    // Operações de agregação
    let soma: i64 = (1..=100).sum();
    let produto: i64 = (1..=10).product();
    let contagem = (1..=1000).filter(|x| x % 7 == 0).count();

    println!("Soma 1..=100: {}", soma);      // 5050
    println!("Produto 1..=10: {}", produto);   // 3628800 (10!)
    println!("Múltiplos de 7 até 1000: {}", contagem);

    // zip com dois ranges
    let pares: Vec<(i32, i32)> = (0..5).zip(10..15).collect();
    println!("Zip: {:?}", pares);
    // [(0, 10), (1, 11), (2, 12), (3, 13), (4, 14)]

    // enumerate (equivalente a zip com 0..)
    for (i, letra) in ('a'..='e').enumerate() {
        println!("  {}: {}", i, letra);
    }

    // Criar string com range
    let alfabeto: String = ('A'..='Z').collect();
    println!("Alfabeto: {}", alfabeto);

    // flat_map com ranges
    let tabuada: Vec<String> = (1..=3)
        .flat_map(|a| (1..=3).map(move |b| format!("{}x{}={}", a, b, a * b)))
        .collect();
    println!("Tabuada: {:?}", tabuada);
}

5. Ranges em Coleções Ordenadas (BTreeMap/BTreeSet)

use std::collections::BTreeMap;
use std::collections::BTreeSet;

fn main() {
    // BTreeMap com range queries
    let mut temperaturas: BTreeMap<u32, f64> = BTreeMap::new();
    for hora in 0..24 {
        let temp = 15.0 + 10.0 * ((hora as f64 - 14.0) / 12.0 * std::f64::consts::PI).sin();
        temperaturas.insert(hora, (temp * 10.0).round() / 10.0);
    }

    // Temperatura entre 8h e 18h
    println!("Temperaturas do dia (8h-18h):");
    for (hora, temp) in temperaturas.range(8..=18) {
        println!("  {:2}h: {:.1}°C", hora, temp);
    }

    // BTreeSet com range
    let numeros: BTreeSet<i32> = (1..=100).collect();

    // Números entre 40 e 50
    let faixa: Vec<&i32> = numeros.range(40..=50).collect();
    println!("\nNúmeros 40-50: {:?}", faixa);

    // Contar elementos em uma faixa
    let qtd = numeros.range(1..=25).count();
    println!("Quantidade 1-25: {}", qtd);

    // Range em match
    let nota = 7;
    let conceito = match nota {
        0..=3 => "Insuficiente",
        4..=5 => "Regular",
        6..=7 => "Bom",
        8..=9 => "Muito Bom",
        10 => "Excelente",
        _ => "Inválido",
    };
    println!("\nNota {}: {}", nota, conceito);
}

Características de Desempenho

Ranges são zero-cost abstractions em Rust. O compilador otimiza loops com ranges para o mesmo código de máquina que um loop C com contador.

// Estas duas versões geram código de máquina idêntico:

// Versão Rust idiomática
fn soma_range(n: i64) -> i64 {
    (1..=n).sum()
}

// Versão "manual"
fn soma_manual(n: i64) -> i64 {
    let mut total = 0;
    let mut i = 1;
    while i <= n {
        total += i;
        i += 1;
    }
    total
}

fn main() {
    println!("{}", soma_range(100));  // 5050
    println!("{}", soma_manual(100)); // 5050
}
AspectoCusto
Criação de rangeO(1), sem alocação
Iteração for x in a..bO(b-a), zero overhead
containsO(1) — simples comparação
countO(1) para Range, O(n) para filtrado
sumO(n) para iteração, O(1) se otimizado
Fatiamento &arr[a..b]O(1) — retorna ponteiro + tamanho

Memória: Um Range<i32> ocupa apenas 8 bytes (dois i32). Um RangeInclusive<i32> ocupa 12 bytes (dois i32 + flag de exaustão). Ranges não alocam memória no heap.

Ranges vs Outras Formas de Iteração

AbordagemQuando Usar
for i in 0..nIteração por índice
for x in &coleçãoIteração por referência
for x in coleçãoIteração consumindo (move)
(0..n).map(f)Transformação funcional
(0..n).filter(f)Filtragem
loop { ... }Loop infinito com controle manual

Veja Também