Como passar em uma entrevista sobre Rust: principais perguntas de nível avançado
Rust é uma das linguagens de programação de sistemas mais requisitadas, e a demanda por desenvolvedores sêniores continua crescendo. Se você já domina a sintaxe básica e o sistema de propriedade, na entrevista encontrará perguntas que testam uma compreensão profunda de código unsafe, assincronicidade, tempos de vida complexos e metaprogramação. Neste artigo, analisaremos os tópicos principais e forneceremos exemplos de código para ajudá-lo a passar na entrevista com confiança.
1. Unsafe Rust: quando e por quê?
Uma das perguntas mais comuns em entrevistas avançadas é: "Explique quando você usaria unsafe e quais garantias são violadas".
A resposta deve incluir as quatro principais capacidades que o unsafe oferece:
- Desreferenciar ponteiros brutos (
*const Te*mut T) - Chamar funções unsafe (por exemplo, FFI)
- Acessar variáveis estáticas mutáveis
- Implementar traits unsafe
Exemplo: implementação de um array dinâmico simples com gerenciamento manual de memória.
use std::alloc::{alloc, dealloc, Layout};use std::ptr;
struct MyVec { ptr: *mut i32, len: usize, cap: usize,}
impl MyVec { fn new() -> Self { MyVec { ptr: ptr::null_mut(), len: 0, cap: 0, } }
fn push(&mut self, val: i32) { if self.len == self.cap { let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 }; let new_layout = Layout::array::<i32>(new_cap).unwrap(); let new_ptr = unsafe { alloc(new_layout) as *mut i32 }; // copiar dados antigos unsafe { ptr::copy_nonoverlapping(self.ptr, new_ptr, self.len); if !self.ptr.is_null() { let old_layout = Layout::array::<i32>(self.cap).unwrap(); dealloc(self.ptr as *mut u8, old_layout); } } self.ptr = new_ptr; self.cap = new_cap; } unsafe { ptr::write(self.ptr.add(self.len), val); } self.len += 1; }}
impl Drop for MyVec { fn drop(&mut self) { if !self.ptr.is_null() { unsafe { let layout = Layout::array::<i32>(self.cap).unwrap(); dealloc(self.ptr as *mut u8, layout); } } }}Dica: sempre enfatize que unsafe não é desabilitar verificações, mas assumir a responsabilidade pelos invariantes.
2. Lifetimes e variância: como o compilador verifica referências
Em nível avançado, pedirão que você explique o que é variância (covariância, contravariância, invariância) no contexto de tempos de vida. Isso é a chave para entender por que alguns tipos não compilam.
- Covariância:
&'a T— se 'a for maior que 'b, então&'a Tpode ser usado como&'b T. - Contravariância:
fn(T)— se 'a for maior que 'b, entãofn(&'b T)pode ser usado comofn(&'a T). - Invariância:
&mut T— o tempo de vida deve corresponder exatamente.
Exemplo: por que o código a seguir não compila?
fn foo<'a>(x: &'a mut &'a str, y: &'a str) { *x = y;}
fn main() { let mut s = "hello"; let r = &s; foo(&mut s, r); // erro!}Resposta: &mut T é invariante em T, portanto o tempo de vida da referência interna deve corresponder ao tempo de vida da referência externa. O compilador não pode "encurtar" o tempo de vida de r.