Skip to main content

Intervalos no Rust: ..=, .. e detalhes relacionados | Rust

  • Intervalos no Rust: ..=, .. e detalhes relacionados | Rust

No Rust, .. e ..= são operadores usados para criar intervalos.

Eles aparecem muito em:

  • for
  • slices
  • match
  • alguns cenários com coleções e iteradores

A diferença principal entre eles é simples:

  • ..não inclui o valor final
  • ..=inclui o valor final

.. e ..= na prática

Intervalo exclusivo no fim: ..

fn main() {
for i in 1..5 {
println!("{i}");
}
}

Saída:

1
2
3
4

Aqui o 5 não entra.


Intervalo inclusivo no fim: ..=

fn main() {
for i in 1..=5 {
println!("{i}");
}
}

Saída:

1
2
3
4
5

Aqui o 5 entra.


Regra mental rápida

  • 1..5 → de 1 até antes do 5
  • 1..=5 → de 1 até o 5

Isso é muito usado com for

Exemplo com ..

fn main() {
for i in 0..3 {
println!("Volta {i}");
}
}

Saída:

Volta 0
Volta 1
Volta 2

Exemplo com ..=

fn main() {
for i in 0..=3 {
println!("Volta {i}");
}
}

Saída:

Volta 0
Volta 1
Volta 2
Volta 3

Por que o Rust usa muito ..?

Porque em programação é muito comum trabalhar com:

  • índice inicial incluso
  • índice final exclusivo

Isso encaixa muito bem com:

  • tamanho de arrays
  • slices
  • contagem baseada em zero

Exemplo:

fn main() {
let numeros = [10, 20, 30, 40, 50];

for i in 0..numeros.len() {
println!("{}", numeros[i]);
}
}

Se o array tem 5 elementos, numeros.len() é 5, então 0..5 percorre:

  • 0
  • 1
  • 2
  • 3
  • 4

Que são índices válidos.


Diferença entre Range e RangeInclusive

Por baixo dos panos, Rust trata esses dois formatos como tipos diferentes:

  • 1..5Range
  • 1..=5RangeInclusive

Na prática, para começo de estudo, pense assim:

  • Range = fim exclusivo
  • RangeInclusive = fim inclusivo

Outros formatos de intervalo

Além de inicio..fim e inicio..=fim, existem formas parciais.

..fim

Vai do começo até antes de fim.

Muito usado em slices:

fn main() {
let valores = [10, 20, 30, 40, 50];
let parte = &valores[..3];

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

Saída:

[10, 20, 30]

Equivale a:

&valores[0..3]

..=fim

Vai do começo até fim, incluindo fim.

fn main() {
let valores = [10, 20, 30, 40, 50];
let parte = &valores[..=2];

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

Saída:

[10, 20, 30]

inicio..

Vai de inicio até o final.

fn main() {
let valores = [10, 20, 30, 40, 50];
let parte = &valores[2..];

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

Saída:

[30, 40, 50]

..

Pega tudo.

fn main() {
let valores = [10, 20, 30];
let parte = &valores[..];

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

Saída:

[10, 20, 30]

Usando com slices

Isso aqui é muito importante:

fn main() {
let texto = String::from("Rust");
let parte = &texto[0..2];

println!("{parte}");
}

Em arrays e vetores, os intervalos trabalham com índices.

Em strings, a ideia também existe, mas há um detalhe:

  • String em Rust é UTF-8
  • nem todo caractere ocupa 1 byte
  • então cortar string por índice pode falhar se você cortar no meio de um caractere

Por isso, slicing de string exige mais cuidado.


Uso em match

Você também pode usar intervalos em padrões:

fn main() {
let nota = 8;

match nota {
0..=4 => println!("Ruim"),
5..=7 => println!("Média"),
8..=10 => println!("Boa"),
_ => println!("Inválida"),
}
}

Aqui:

  • 0..=4 inclui 4
  • 5..=7 inclui 7
  • 8..=10 inclui 10

Quando usar .. e quando usar ..=

Use .. quando:

  • estiver lidando com índices
  • quiser “até antes de”
  • estiver percorrendo de 0 até len()

Exemplo clássico:

for i in 0..vec.len() {
println!("{}", vec[i]);
}

Use ..= quando:

  • quiser incluir o último valor
  • a ideia for “de X até Y”
  • fizer mais sentido semântico incluir o fim

Exemplo:

for i in 1..=10 {
println!("{i}");
}

Comparação lado a lado

0..5

0, 1, 2, 3, 4

0..=5

0, 1, 2, 3, 4, 5

Erros comuns

1. Achar que .. inclui o final

Errado:

for i in 1..5 {
println!("{i}");
}

Isso não imprime o 5.


2. Usar ..= com índice final inválido

Exemplo perigoso:

let v = vec![1, 2, 3];
let parte = &v[..=3];

Isso tenta incluir o índice 3, mas os índices válidos são:

  • 0
  • 1
  • 2

Então vai falhar.


3. Confundir valor com índice

Uma coisa é percorrer valores:

for i in 1..=3 {
println!("{i}");
}

Outra é acessar posições:

let v = vec![10, 20, 30];
for i in 0..v.len() {
println!("{}", v[i]);
}

No primeiro caso, i é um número da sequência. No segundo, i é índice.


Observação importante sobre performance

Esses intervalos são muito idiomáticos no Rust e são usados junto com iteradores.

Exemplo:

for i in 1..=5 {
println!("{i}");
}

Isso é natural na linguagem e normalmente é otimizado muito bem.


Forma mais idiomática com coleções

Embora 0..vec.len() funcione, muitas vezes é melhor iterar direto sobre a coleção:

fn main() {
let valores = vec![10, 20, 30];

for valor in &valores {
println!("{valor}");
}
}

Assim você evita trabalhar com índice quando não precisa.

Mas o intervalo continua sendo muito útil quando:

  • você realmente precisa do índice
  • quer controlar começo e fim
  • quer percorrer um range numérico

Resumo

  • .. → intervalo com fim exclusivo
  • ..= → intervalo com fim inclusivo
  • ..fim → do começo até antes de fim
  • ..=fim → do começo até fim
  • inicio.. → de inicio até o final
  • .. sozinho em slice → tudo

Exemplos rápidos

1..5

resultado:

1, 2, 3, 4
1..=5

resultado:

1, 2, 3, 4, 5
&v[..3]

pega do início até antes do índice 3

&v[2..]

pega do índice 2 até o final


Exemplos finais

Loop com ..

fn main() {
for i in 0..5 {
println!("{i}");
}
}

Loop com ..=

fn main() {
for i in 0..=5 {
println!("{i}");
}
}

Slice com intervalo

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

println!("{:?}", &v[1..4]);
}

Saída:

[2, 3, 4]

Match com intervalo

fn main() {
let idade = 17;

match idade {
0..=12 => println!("Criança"),
13..=17 => println!("Adolescente"),
_ => println!("Adulto"),
}
}