Operadores
por Beckett HollisEntenda o conceito
Operadores são os símbolos que combinam valores: os aritméticos (+, -, *, /, %), os de comparação e os lógicos, que você já usou nas condicionais, os de atribuição composta (+=, -=, *=, /=, %=) e os bit a bit (&, |, ^, !, <<, >>). Rust não tem ++ nem --, e potência não é operador: usa-se o método pow (inteiros) ou powf (decimais).
Numa expressão com vários operadores, a precedência define a ordem: as vem antes de *, / e %, que vêm antes de + e -; depois vêm os deslocamentos e os bit a bit e, só então, as comparações. Por isso x & MASCARA == 0 em Rust significa (x & MASCARA) == 0. Na dúvida, parênteses deixam a intenção explícita.
Os dois lados de um operador precisam ter o mesmo tipo, e o resultado também tem esse tipo. Quando ele não cabe (um u8 passando de 255, por exemplo), há estouro: em modo debug o programa entra em pânico e em modo release o valor dá a volta. Para controlar isso, os inteiros têm métodos como checked_add, saturating_sub e wrapping_add. Com negativos, % mantém o sinal do dividendo, e rem_euclid devolve sempre um resto positivo.
- Rust não tem
++nem--: use+=e-=. Potência é método:7_i32.pow(2)ou2.0_f64.powf(0.5). - Precedência:
as, depois*/%, depois+-, depois<<>>, depois&^|e só então as comparações. Parênteses resolvem qualquer dúvida. &testa e desliga bits,|liga,^alterna,!inverte todos e<</>>deslocam; com uma constante por bit, umu8guarda até oito opções.- Estouro de inteiro causa pânico em debug e dá a volta em release;
checked_*devolveOption,saturating_*para no limite ewrapping_*dá a volta de propósito. - Com negativos,
-7 % 3dá-1;(-7_i32).rem_euclid(3)dá2, útil para contas circulares como dias da semana.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Média do boletim com precedência e parênteses
// Operadores aritméticos, precedência e parênteses.
fn main() {
let prova: f64 = 8.0;
let trabalho: f64 = 6.5;
let participacao: f64 = 9.0;
// * e / vêm antes de + e -; os parênteses mudam a ordem.
let sem_parenteses = prova + trabalho / 2.0;
let com_parenteses = (prova + trabalho) / 2.0;
println!("Sem parênteses: {sem_parenteses}");
println!("Com parênteses: {com_parenteses}");
// Média ponderada: pesos 5, 3 e 2.
let media = (prova * 5.0 + trabalho * 3.0 + participacao * 2.0) / 10.0;
println!("Média ponderada: {media:.2}");
// Entre inteiros, / descarta a fração e % devolve o resto.
let aulas: i32 = 75;
let faltas: i32 = 11;
let presenca = (aulas - faltas) * 100 / aulas;
println!(
"Presença: {presenca}% (resto {})",
(aulas - faltas) * 100 % aulas
);
// Rust não tem ++ nem --: use += e -=.
let mut pontos_extras = 0;
pontos_extras += 1;
pontos_extras *= 3;
println!("Pontos extras: {pontos_extras}");
// Potência é um método, não um operador.
let quadrado = 7_i32.pow(2);
let raiz = 2.0_f64.powf(0.5);
println!("7² = {quadrado}, 2^0,5 = {raiz:.3}");
// O - unário inverte o sinal.
let ajuste = -0.5;
println!("Média ajustada: {:.2}", media + ajuste);
}
As duas primeiras contas usam os mesmos números e dão resultados diferentes: sem parênteses, trabalho / 2.0 é calculado antes da soma (11.25); com parênteses, a soma vem primeiro (7.25). A média ponderada junta multiplicações e somas e só divide no fim, graças aos parênteses.
Com inteiros, (aulas - faltas) * 100 / aulas multiplica antes de dividir para não perder precisão, e % mostra o resto que a divisão inteira descartou. O exemplo também mostra que Rust troca ++ por += e *=, e que potência é feita com os métodos pow e powf, com o tipo do literal indicado por sufixo (7_i32, 2.0_f64).
02Habilidades do personagem com operadores bit a bit
// Operadores bit a bit: cada bit de um u8 guarda uma opção.
const MOVER: u8 = 1 << 0; // 0000_0001
const ATACAR: u8 = 1 << 1; // 0000_0010
const VOAR: u8 = 1 << 2; // 0000_0100
const NADAR: u8 = 1 << 3; // 0000_1000
fn main() {
// | junta opções num único valor.
let mut habilidades = MOVER | ATACAR;
println!("Início: {habilidades:08b}");
// |= liga um bit sem mexer nos outros.
habilidades |= NADAR;
println!("Com nado: {habilidades:08b}");
// & testa se um bit está ligado.
let pode_voar = habilidades & VOAR != 0;
let pode_nadar = habilidades & NADAR != 0;
println!("Voa? {pode_voar}, nada? {pode_nadar}");
// &= com !bit desliga um bit (! inverte todos os bits do u8).
habilidades &= !ATACAR;
println!("Sem ataque: {habilidades:08b}");
// ^= alterna: liga se estava desligado e vice-versa.
habilidades ^= VOAR;
println!("Voo trocado: {habilidades:08b}");
// count_ones conta quantos bits estão ligados.
println!("Habilidades ativas: {}", habilidades.count_ones());
// << e >> deslocam bits: multiplicam ou dividem por potências de 2.
let moedas: u32 = 5;
println!("{moedas} << 3 = {}, 40 >> 2 = {}", moedas << 3, 40_u32 >> 2);
}
Cada habilidade é uma constante com um único bit ligado, criada com 1 << n. O | junta várias num só u8, |= liga mais uma, & testa se uma está presente, &= !ATACAR desliga uma sem mexer nas outras e ^= alterna. O formato {habilidades:08b} imprime os oito bits para acompanhar cada mudança.
Repare em habilidades & VOAR != 0: em Rust, os operadores bit a bit têm precedência maior que as comparações, então a expressão significa (habilidades & VOAR) != 0. No fim, << e >> mostram que deslocar bits equivale a multiplicar ou dividir por potências de 2 (5 << 3 é 5 * 8).
03Estoque sem estouro com checked, saturating e wrapping
// Estouro de inteiros: checked, saturating e wrapping.
fn main() {
let estoque: u8 = 250;
let chegada: u8 = 10;
// checked_add devolve Some(valor) ou None quando o resultado não cabe.
match estoque.checked_add(chegada) {
Some(total) => println!("Novo estoque: {total}"),
None => println!("Estouro: {estoque} + {chegada} não cabe em u8"),
}
// saturating_* para no limite do tipo em vez de estourar.
println!("Saturando a soma: {}", estoque.saturating_add(chegada));
let reservados: u8 = 3;
println!("Saturando a subtração: {}", reservados.saturating_sub(5));
// wrapping_* dá a volta, como um hodômetro.
println!("Dando a volta: {}", estoque.wrapping_add(chegada));
// checked_sub evita tirar mais do que existe.
let pedido: u8 = 4;
match reservados.checked_sub(pedido) {
Some(resto) => println!("Sobram {resto} reservados"),
None => println!("Pedido de {pedido} recusado: só há {reservados}"),
}
// Com negativos, % mantém o sinal; rem_euclid devolve sempre >= 0.
let saldo: i32 = -7;
println!("-7 % 3 = {}", saldo % 3);
println!("(-7).rem_euclid(3) = {}", saldo.rem_euclid(3));
println!(
"-7 / 3 = {}, (-7).div_euclid(3) = {}",
saldo / 3,
saldo.div_euclid(3)
);
}
Um u8 guarda no máximo 255, e 250 + 10 não cabe. Com + comum, o programa entraria em pânico em modo debug. Os métodos deixam a escolha explícita: checked_add devolve None e o match trata o caso, saturating_add para em 255 e wrapping_add dá a volta (260 - 256 = 4), como um hodômetro. O mesmo vale para a subtração com saturating_sub e checked_sub.
As últimas linhas mostram a divisão com negativos: % e / arredondam em direção a zero (-7 % 3 dá -1), enquanto rem_euclid e div_euclid mantêm o resto sempre positivo. Note os parênteses em (-7).rem_euclid(3): sem eles, o método seria aplicado ao 7 antes do sinal de menos.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Fatias de pizza na festaFácil
Numa festa há 3 pizzas de 8 fatias para 7 convidados. Mostre quantas fatias cada um recebe e quantas sobram; depois chegam mais 2 convidados e a divisão é refeita, terminando com a prova real da divisão.
- As quantidades são
u32, econvidadosé a única variávelmut, aumentada com+=. - As fatias por pessoa usam
/e as que sobram usam%. - A última linha imprime o
booldepor_pessoa * convidados + sobra == total. - A saída é idêntica à esperada.
fn main() {
let pizzas: u32 = 3;
let fatias_por_pizza: u32 = 8;
let mut convidados: u32 = 7;
// total de fatias, divisão para 7 convidados
// chegam mais 2 convidados: refaça a divisão
// prova real: quociente * divisor + resto == total
}
Total de fatias: 24
Para 7 convidados: 3 cada, sobram 3
Para 9 convidados: 2 cada, sobram 6
Conferência: true
Ver dica
A divisão entre inteiros já descarta a fração; o operador que devolve exatamente o que ela jogou fora é o que você precisa para as sobras.
Ver resposta
fn main() {
let pizzas: u32 = 3;
let fatias_por_pizza: u32 = 8;
let mut convidados: u32 = 7;
// * multiplica; / e % dividem entre inteiros
let total = pizzas * fatias_por_pizza;
println!("Total de fatias: {total}");
println!(
"Para {convidados} convidados: {} cada, sobram {}",
total / convidados,
total % convidados
);
// Rust não tem ++: += soma e guarda na mesma variável
convidados += 2;
let por_pessoa = total / convidados;
let sobra = total % convidados;
println!("Para {convidados} convidados: {por_pessoa} cada, sobram {sobra}");
// Prova real da divisão: quociente * divisor + resto
println!("Conferência: {}", por_pessoa * convidados + sobra == total);
}
Passo a passo
- As três variáveis são
u32porque fatias e pessoas nunca são negativas;convidadoslevamutporque o número de convidados muda no meio do programa. pizzas * fatias_por_pizzacalcula o total (24) uma vez só e guarda o resultado, em vez de repetir a multiplicação em cada conta.total / convidadosé uma divisão inteira: 24 / 7 dá 3, e a fração é descartada;total % convidadosdevolve o que sobra dessa divisão (3).convidados += 2;é a forma curta deconvidados = convidados + 2;. Rust não tem++, então o operador composto é o jeito idiomático de somar.- Com 9 convidados, a mesma dupla
/e%dá 2 fatias para cada um e 6 de sobra. - A última linha faz a prova real:
por_pessoa * convidados + sobra == total. A multiplicação e a soma acontecem antes da comparação==, que devolve umbool.
Por que funciona
Para inteiros, / e % são um par: o quociente diz quantas fatias inteiras cada pessoa recebe e o resto diz quantas ficam na travessa. Sempre vale quociente * divisor + resto == dividendo, e é isso que a conferência imprime como true. A precedência dos operadores garante que as contas aritméticas sejam feitas antes do ==, sem precisar de parênteses.
Erros comuns
- Usar
++:convidados++;não compila (Rust has no postfix increment operator). Useconvidados += 1;ou, aqui,convidados += 2;. - Esperar fração na divisão inteira:
24 / 9dá2, não2.67. Se precisar das casas decimais, converta os dois lados comas f64antes de dividir. - Esquecer o
mut: semlet mut convidados, a linhaconvidados += 2;falha comcannot assign twice to immutable variable. - Dividir por zero: com
convidadosigual a 0, a divisão causa pânico (attempt to divide by zero). Quando o divisor puder ser zero, teste antes comif.
Outra forma de resolver
Em vez de / e % separados, dá para usar os métodos total.div_euclid(convidados) e total.rem_euclid(convidados). Para números positivos o resultado é o mesmo; eles são preferíveis quando os valores podem ser negativos, porque o resto nunca sai menor que zero.
Saída esperada
Total de fatias: 24
Para 7 convidados: 3 cada, sobram 3
Para 9 convidados: 2 cada, sobram 6
Conferência: true
02Selos de um produto da lojaMédio
Guarde os selos de um produto (frete grátis, promoção, esgotado e importado) como bits de um único u8 e acompanhe o produto do cadastro até a reposição, mostrando os bits a cada etapa e, no fim, se ele aparece na vitrine.
- Cada selo é uma constante
u8criada com1 << n, na ordem: frete grátis, promoção, esgotado, importado. - A promoção liga dois selos com um único
|=; ao esgotar, uma só expressão liga esgotado e desliga promoção e frete grátis usando|,&e!. - A reposição usa
^=, e as respostas finais usam&comparado com zero ecount_ones(). - Os bits são impressos com
{selos:04b}e a saída é idêntica à esperada.
const FRETE_GRATIS: u8 = 1 << 0;
const PROMOCAO: u8 = 1 << 1;
const ESGOTADO: u8 = 1 << 2;
const IMPORTADO: u8 = 1 << 3;
fn main() {
let mut selos: u8 = IMPORTADO;
println!("Cadastro: {selos:04b}");
// promoção, esgotou e reposição
// na vitrine? frete grátis? quantos selos ativos?
}
Cadastro: 1000
Promoção: 1011
Esgotou: 1100
Reposição: 1000
Na vitrine? true, frete grátis? false
Selos ativos: 1
Ver dica
Para desligar bits sem tocar nos outros, combine & com uma máscara em que só os bits a apagar valem 0; o ! constrói essa máscara.
Ver resposta
// Um bit para cada selo do produto
const FRETE_GRATIS: u8 = 1 << 0;
const PROMOCAO: u8 = 1 << 1;
const ESGOTADO: u8 = 1 << 2;
const IMPORTADO: u8 = 1 << 3;
fn main() {
let mut selos: u8 = IMPORTADO;
println!("Cadastro: {selos:04b}");
// |= liga os dois selos de uma vez
selos |= PROMOCAO | FRETE_GRATIS;
println!("Promoção: {selos:04b}");
// Liga ESGOTADO e, com & e !, desliga promoção e frete grátis
selos = (selos | ESGOTADO) & !(PROMOCAO | FRETE_GRATIS);
println!("Esgotou: {selos:04b}");
// ^= inverte o bit: o produto volta ao estoque
selos ^= ESGOTADO;
println!("Reposição: {selos:04b}");
// & isola um bit; != 0 transforma o resultado em bool
let na_vitrine = selos & ESGOTADO == 0;
let frete_gratis = selos & FRETE_GRATIS != 0;
println!("Na vitrine? {na_vitrine}, frete grátis? {frete_gratis}");
println!("Selos ativos: {}", selos.count_ones());
}
Passo a passo
- Cada constante liga um único bit com
1 << n:FRETE_GRATISé0001,PROMOCAOé0010,ESGOTADOé0100eIMPORTADOé1000. Assim, quatro selos cabem num sóu8. selos |= PROMOCAO | FRETE_GRATIS;primeiro junta os dois selos com|(0011) e depois liga esses bits emselos, sem mexer no bit de importado.(selos | ESGOTADO) & !(PROMOCAO | FRETE_GRATIS)faz duas coisas: o|liga o bit de esgotado, e o&com a máscara invertida por!desliga promoção e frete grátis.selos ^= ESGOTADO;inverte só aquele bit: como ele estava ligado, passa a desligado e o produto volta a ter só o selo de importado.selos & ESGOTADO == 0isola o bit de esgotado e compara com zero. Em Rust,&tem precedência maior que==, então a conta é feita antes da comparação.count_ones()conta os bits ligados; sobrou 1, o de importado.
Por que funciona
Os operadores bit a bit trabalham em cada posição do número separadamente. | liga bits (basta um lado ter 1), & mantém só os bits presentes nos dois lados, ! inverte todos e ^ inverte onde o outro lado tem 1. Com uma constante por bit, cada operação mexe só nos selos desejados e deixa os outros intactos, e {selos:04b} mostra o resultado em binário para conferir.
Erros comuns
- Somar em vez de usar
|:selos + PROMOCAOfunciona quando o bit está desligado, mas, se ele já estiver ligado, a soma vai para o bit seguinte e liga o selo errado. Use|ou|=. - Desligar com
^:selos ^= PROMOCAOdesliga só se o bit estiver ligado; se não estiver, ele liga. Para desligar com certeza, useselos &= !PROMOCAO. - Usar
!=sem o&:selos != ESGOTADOcompara o número inteiro, não o bit. Com1100, a expressão dátruemesmo com o produto esgotado. Isole o bit:selos & ESGOTADO != 0.
Outra forma de resolver
A passagem para esgotado também pode ser feita em dois comandos: selos |= ESGOTADO; e depois selos &= !(PROMOCAO | FRETE_GRATIS);. Fica mais fácil de ler quando cada passo tem um motivo diferente; a expressão única é preferível quando a mudança deve acontecer de uma vez, como uma única regra de negócio.
Saída esperada
Cadastro: 1000
Promoção: 1011
Esgotou: 1100
Reposição: 1000
Na vitrine? true, frete grátis? false
Selos ativos: 1
03Devolução atrasada na bibliotecaDesafio
Um leitor está há 17 dias com um livro cujo prazo é de 10 dias, e hoje é sexta. Descubra em que dia da semana ele retirou o livro e o dia do vencimento, calcule o atraso, desconte 3 pontos de fidelidade por dia sem deixar o saldo estourar e tente reservar 2 exemplares de um título que só tem 1.
- Os dias da semana são
i32(0 = domingo, 6 = sábado), e a retirada é calculada comrem_euclid(7), porque a subtração fica negativa; o vencimento usa%. - Os nomes dos dias saem de um array constante
DIAS, indexado comas usize. - Os pontos (12) são
u8, reduzidos comsaturating_sub; a reserva usachecked_subcom ummatchsobreSomeeNone. - A saída é idêntica à esperada.
const DIAS: [&str; 7] = [
"domingo", "segunda", "terça", "quarta", "quinta", "sexta", "sábado",
];
const PRAZO_DIAS: i32 = 10;
const PENALIDADE_POR_DIA: u8 = 3;
fn main() {
let hoje: i32 = 5;
let dias_com_livro: i32 = 17;
let pontos: u8 = 12;
let exemplares: u8 = 1;
let pedido_reserva: u8 = 2;
// dia da retirada, vencimento e atraso
// penalidade nos pontos sem estourar o u8
// reserva com checked_sub
}
Hoje: sexta
Retirada: terça
Vencimento: sexta
Dias de atraso: 7
Pontos de fidelidade: 0 (perdeu 21)
Reserva de 2 recusada: só há 1
Ver dica
Calcule 5 - 17 e veja o que % faz com um número negativo; existe um método de resto que nunca devolve valor menor que zero.
Ver resposta
// Dias da semana: 0 = domingo, 6 = sábado
const DIAS: [&str; 7] = [
"domingo", "segunda", "terça", "quarta", "quinta", "sexta", "sábado",
];
const PRAZO_DIAS: i32 = 10;
const PENALIDADE_POR_DIA: u8 = 3;
fn main() {
let hoje: i32 = 5;
let dias_com_livro: i32 = 17;
let pontos: u8 = 12;
let exemplares: u8 = 1;
let pedido_reserva: u8 = 2;
// 5 - 17 dá -12; rem_euclid devolve um dia válido de 0 a 6
let retirada = (hoje - dias_com_livro).rem_euclid(7);
// A soma é positiva, então % basta
let vencimento = (retirada + PRAZO_DIAS) % 7;
println!("Hoje: {}", DIAS[hoje as usize]);
println!("Retirada: {}", DIAS[retirada as usize]);
println!("Vencimento: {}", DIAS[vencimento as usize]);
let atraso = dias_com_livro - PRAZO_DIAS;
println!("Dias de atraso: {atraso}");
// as vem antes de *: (atraso as u8) * PENALIDADE_POR_DIA
let penalidade = atraso as u8 * PENALIDADE_POR_DIA;
// saturating_sub para em 0 em vez de estourar o u8
let restantes = pontos.saturating_sub(penalidade);
println!("Pontos de fidelidade: {restantes} (perdeu {penalidade})");
// checked_sub devolve None quando não há exemplares suficientes
match exemplares.checked_sub(pedido_reserva) {
Some(sobra) => println!("Reserva confirmada; sobram {sobra}"),
None => println!("Reserva de {pedido_reserva} recusada: só há {exemplares}"),
}
}
Passo a passo
DIASé um array constante com os sete nomes; um número de 0 a 6 convertido comas usizeserve de índice (DIAS[5]é"sexta"). Os dias ficam emi32porque a conta da retirada passa por um valor negativo.hoje - dias_com_livrodá-12. Com%, o resultado seria-5, que não é um dia válido;rem_euclid(7)devolve sempre um número de 0 a 6, aqui2(terça).(retirada + PRAZO_DIAS) % 7soma o prazo e volta ao início da semana quando passa de sábado:(2 + 10) % 7dá5, sexta. Como a soma é positiva,%basta.atrasoé a diferença entre os dias com o livro e o prazo:17 - 10dá 7.atraso as u8 * PENALIDADE_POR_DIAconverte primeiro e multiplica depois, porqueastem precedência maior que*. A penalidade fica em 21 pontos.pontos.saturating_sub(penalidade)tira 21 de 12 sem estourar: o resultado para em 0, o menor valor de umu8.checked_subdevolveSome(resto)quando a subtração cabe no tipo eNonequando não cabe. Omatchtrata os dois casos, e a reserva de 2 exemplares é recusada porque só há 1.
Por que funciona
Cada operação escolhe o comportamento certo para o que pode dar errado. O resto euclidiano (rem_euclid) faz a aritmética circular dos dias funcionar também para trás no tempo. Nos pontos, perder mais do que se tem significa zerar, e é isso que a saturação faz. Na reserva, faltar exemplar é um caso de negócio que precisa de resposta, por isso checked_sub devolve um Option e o match obriga a tratar o None. Em nenhum ponto o programa depende do estouro silencioso de um inteiro.
Erros comuns
- Usar
%com negativos:(hoje - dias_com_livro) % 7dá-5, e-5 as usizevira um número enorme; o acesso ao array causa pânico comindex out of bounds. Userem_euclid(7). - Subtrair
u8direto:pontos - penalidadecom 12 e 21 causa pânico em modo debug (attempt to subtract with overflow) e, em modo release, dá a volta e devolve 247. Usesaturating_subouchecked_sub. - Chamar método num literal negativo:
-12.rem_euclid(7)não faz o que parece, porque o método é aplicado antes do-unário; o compilador ainda reclama do tipo ambíguo (can't call method rem_euclid on ambiguous numeric type). Escreva(-12_i32).rem_euclid(7). - Ignorar o
None: usarexemplares.checked_sub(pedido_reserva).unwrap()encerra o programa com pânico quando falta exemplar. Trate os dois casos commatch.
Outra forma de resolver
A reserva poderia ser decidida com um if pedido_reserva <= exemplares { ... } else { ... } antes da subtração, e o - comum ficaria seguro dentro do ramo verdadeiro. Funciona bem quando a regra é simples; checked_sub é preferível quando a conta faz parte de um cálculo maior, porque a própria operação avisa que não coube e o compilador não deixa esquecer esse caso.
Saída esperada
Hoje: sexta
Retirada: terça
Vencimento: sexta
Dias de atraso: 7
Pontos de fidelidade: 0 (perdeu 21)
Reserva de 2 recusada: só há 1