O que é o operador de interrogação em Rust? Análise para iniciantes
\n\nSe você está começando a estudar Rust, provavelmente já se deparou com código que termina com um símbolo estranho ?. Parece mais ou menos assim:
fn read_file(path: &str) -> Result<String, io::Error> {\n let mut file = File::open(path)?;\n let mut contents = String::new();\n file.read_to_string(&mut contents)?;\n Ok(contents)\n}\n\nOu assim:
\n\nfn get_user_name(user_id: u32) -> Option<String> {\n let user = find_user(user_id)?;\n Some(user.name)\n}\n\nEste pequeno símbolo — é o operador de interrogação (question mark operator). Para um iniciante, parece mágica: o código fica mais curto, mais limpo e os erros desaparecem em algum lugar. Mas o que realmente acontece por trás deste sinal? Vamos entender de uma vez por todas.
\n\nA propósito, esta pergunta é tão popular que no Stack Overflow já acumulou milhares de visualizações. Não é surpresa — o operador ? muda a forma como você escreve código em Rust.
Por que o operador de interrogação é necessário?
\n\nRust é famoso por seu sistema de tipos e segurança. Ele não tem exceções (exceptions), como Python ou Java. Em vez disso, os erros são apenas valores. Para isso, existem dois tipos especiais:
\n\n- \n
Result<T, E>— a operação pode ser bem-sucedida (Ok(T)) ou resultar em erro (Err(E)). \n Option<T>— o valor pode existir (Some(T)) ou estar ausente (None). \n
O problema é que lidar manualmente com esses tipos é tedioso. Imagine que você precisa abrir um arquivo, lê-lo, analisar um JSON e enviá-lo pela rede. Cada etapa pode retornar um erro. Sem o ?, você escreveria montanhas de código com match ou unwrap.
O operador de interrogação resolve esse problema de forma elegante.
\n\nComo funciona o operador ?: explicação simples
\n\nImagine que o operador ? é um assistente inteligente que diz:
- \n
- Se tudo estiver bem — me dê o valor, eu continuo o trabalho. \n
- Se algo der errado — eu imediatamente retorno o erro da função atual. \n
Vamos ver um exemplo concreto. Sem o operador de interrogação:
\n\nuse std::fs::File;\nuse std::io::{self, Read};\n\nfn read_file(path: &str) -> Result<String, io::Error> {\n let file_result = File::open(path);\n \n let mut file = match file_result {\n Ok(f) => f,\n Err(e) => return Err(e),\n };\n \n let mut contents = String::new();\n let read_result = file.read_to_string(&mut contents);\n \n match read_result {\n Ok(_) => Ok(contents),\n Err(e) => return Err(e),\n }\n}\n\nE agora com o operador de interrogação:
\n\nuse std::fs::File;\nuse std::io::{self, Read};\n\nfn read_file(path: &str) -> Result<String, io::Error> {\n let mut file = File::open(path)?;\n let mut contents = String::new();\n file.read_to_string(&mut contents)?;\n Ok(contents)\n}\n\nA diferença é colossal. Em vez de 15 linhas — 5. E isso não é apenas açúcar sintático. O operador ? torna o código mais legível e menos propenso a erros.
Mergulho profundo: o que acontece nos bastidores
\n\nQuando você escreve File::open(path)?, o compilador Rust expande isso para um código mais ou menos assim:
match File::open(path) {\n Ok(val) => val,\n Err(err) => return Err(From::from(err)),\n}\n\nObserve o From::from(err). Este é o ponto-chave. O operador ? não apenas retorna o erro — ele converte automaticamente o tipo de erro usando a trait From.
O que isso significa na prática? Suponha que,