Cómo aprobar una entrevista sobre Rust: las mejores preguntas de nivel avanzado
Rust es uno de los lenguajes de programación de sistemas más demandados, y la necesidad de desarrolladores senior sigue creciendo. Si ya dominas la sintaxis básica y el sistema de propiedad, en la entrevista te esperan preguntas que evalúan una comprensión profunda del código unsafe, la asincronía, los lifetimes complejos y la metaprogramación. En este artículo, analizaremos los temas clave y proporcionaremos ejemplos de código que te ayudarán a superar la entrevista con confianza.
1. Unsafe Rust: ¿cuándo y por qué?
Una de las preguntas más frecuentes en una entrevista avanzada es: «Explica cuándo usarías unsafe y qué garantías se infringen».
La respuesta debe incluir las cuatro capacidades principales que proporciona unsafe:
- Desreferenciar punteros crudos (
*const Ty*mut T) - Llamar a funciones unsafe (por ejemplo, FFI)
- Acceder a variables estáticas mutables
- Implementar traits unsafe
Ejemplo: implementación de un array dinámico simple con gestión manual de memoria.
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 }; // copiamos los datos antiguos 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); } } }}Consejo: siempre enfatiza que unsafe no es desactivar las comprobaciones, sino asumir la responsabilidad de los invariantes.
2. Lifetimes y variance: cómo el compilador verifica las referencias
A nivel avanzado, te pedirán que expliques qué es variance (covarianza, contravarianza, invarianza) en el contexto de los lifetimes. Esta es la clave para entender por qué algunos tipos no compilan.
- Covarianza:
&'a T— si 'a es mayor que 'b, entonces&'a Tse puede usar como&'b T. - Contravarianza:
fn(T)— si 'a es mayor que 'b, entoncesfn(&'b T)se puede usar comofn(&'a T). - Invarianza:
&mut T— el lifetime debe coincidir exactamente.
Ejemplo: ¿por qué el siguiente código no 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); // ¡error!}Respuesta: &mut T es invariante respecto a T, por lo que el lifetime de la referencia interna debe coincidir con el lifetime de la externa. El compilador no puede «acortar» el lifetime de r.