O crivo de Eratóstenes lista todos os primos até um limite superior n, inclusive. Aqui, programas em Rust imprimem os 168 primos menores ou iguais a 1000. O limite está incluído; 0 e 1 não são primos.
Etapas do crivo e por que começar no quadrado
- Aloque
n + 1elementos booleanos para usar os índices de 0 an. A versão básica inicializa todos como verdadeiros e marca 0 e 1 como falsos. Para um limite menor que 2, retorna um resultado vazio antes de acessar o índice 1. - Examine os candidatos
pem ordem crescente, começando por 2. Se a marca ainda for verdadeira,pé primo: marque como falsos os múltiplos a partir de \(p^2\), avançando depemp. Não marque o própriop. - Basta eliminar múltiplos para candidatos que satisfaçam \(p^2\le n\), pois todo composto tem um fator primo menor ou igual à sua raiz quadrada.
- Ao final, percorra de 2 a
ne imprima todos os números cuja marca continua verdadeira. A faixa de saída não se limita à raiz quadrada.
Um múltiplo \(kp\) menor que \(p^2\), com \(2\le k<p\), já foi eliminado por um fator primo de k menor que p. Isso não significa que todos os números menores já tenham sido listados.

Imagem: SKopp, versão atualizada por Suricata, Wikimedia Commons, CC BY-SA 3.0. O arquivo não foi modificado. A ilustração vai de 2 a 120; os programas abaixo usam o limite 1000.
Implementação básica em Rust
| |
Salve como sieve.rs e compile com rustc --edition=2021 sieve.rs -o sieve. No Windows, use sieve.exe como nome do executável. A execução imprime 168 linhas, começando por 2 e terminando em 997. Estes exemplos foram executados com Rust 1.90.0.
sieve(0) e sieve(1) retornam um vetor vazio; sieve(2) retorna [2]. A condição p <= n / p garante que p * p só seja calculado quando o quadrado está dentro do limite. Antes de somar o próximo múltiplo, verifica-se a distância restante. Não é necessário calcular uma raiz quadrada em ponto flutuante.
Versão que elimina apenas candidatos ímpares
Os pares maiores que 2 são compostos. Portanto, podemos marcar 2 como verdadeiro e inicializar apenas os ímpares como candidatos. Para um primo ímpar p, o quadrado também é ímpar; avançar a partir dele em passos de \(2p\) visita somente múltiplos ímpares.
| |
Salve esta versão em outro arquivo e compile. Ela imprime os mesmos 168 primos, retorna um resultado vazio para 0 e 1, e [2] para 2. Não junte as duas funções main no mesmo arquivo.
Esta versão pula parte do trabalho com números pares. Entretanto, inicializar como falso não garante maior velocidade. O tempo depende da inicialização, das escritas, do acesso à memória, do compilador e da máquina. Não há comparação de tempos medidos aqui. O array ainda contém n + 1 elementos para que os índices correspondam diretamente aos números.
Complexidade e limites de uso
No modelo RAM usual, o crivo usa tempo \(O(n\log\log n)\) e espaço \(O(n)\) para as marcas. São estimativas assintóticas de operações e armazenamento, não tempos medidos nem complexidade em bits para inteiros de precisão arbitrária. O exercício do crivo no material de análise de Princeton também apresenta esse crescimento.
Em Rust, bool ocupa um byte, e Vec<T> armazena elementos contíguos. Este Vec<bool> não é um array de bits compactados. A quantidade lógica de marcas é n + 1; os metadados do vetor, a capacidade alocada e o Vec<usize> retornado também exigem memória.
Se n + 1 não puder ser representado, checked_add retorna None, e o expect seguinte provoca um panic. Nos demais casos, ainda é preciso dispor de memória suficiente; limites arbitrariamente grandes não são suportados. Para intervalos grandes, considere um crivo segmentado. Listar primos de um intervalo tem uma finalidade diferente de testar um único inteiro grande.
