Para interactuar con el sistema de archivos en Rust, es fundamental distinguir entre la ruta de un archivo y su información física en el disco. Un objeto Path o PathBuf representa una ruta (como una cadena de texto con formato de dirección), pero no garantiza que el archivo exista o contenga información. Para obtener atributos como el tamaño, las fechas de modificación o el tipo de archivo, debemos consultar los metadatos mediante fs::metadata(path). Es crucial notar que fs::metadata sigue los enlaces simbólicos para obtener información del destino, mientras que fs::symlink_metadata obtiene la información del enlace mismo.
Cuando se requiere listar el contenido de una carpeta, fs::read_dir es la herramienta principal. Esta función devuelve un iterador sobre io::Result<DirEntry>. Cada DirEntry es un objeto optimizado que permite acceder al nombre del archivo (file_name()) y su tipo (file_type()) de manera eficiente sin necesidad de realizar múltiples llamadas al sistema. Para inspeccionar la estructura de un árbol de directorios completo, se implementa un patrón de recursividad: si un elemento es un directorio, la función se llama a sí misma para explorar su interior.
Un error común al realizar recorridos recursivos es entrar en bucles infinitos debido a enlaces simbólicos que apuntan a directorios padres. Además, es vital manejar los tipos de datos de las rutas correctamente: Path es una referencia (borrowed) que no posee la memoria, mientras que PathBuf es una estructura que posee los datos (owned) y permite la manipulación y unión de rutas mediante .join().
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
/// Función que recorre un directorio de forma recursiva
fn recorrer_recursivo(ruta: &Path) -> io::Result<()> {
// Verificamos si la ruta actual es un directorio
if ruta.is_dir() {
println!("Explorando directorio: {:?}", ruta);
// read_dir devuelve un iterador sobre Result<DirEntry>
for entrada in fs::read_dir(ruta)? {
let entrada = entrada?; // Desempaquetamos el Result de la entrada
let path = entrada.path(); // Obtenemos la ruta completa (PathBuf)
let tipo = entrada.file_type()?; // Obtenemos el tipo de entrada (file, dir, symlink)
if tipo.is_dir() {
// Si es un directorio, llamamos a la función recursivamente
recorrer_recursivo(&path)?;
} else {
// Si es un archivo, extraemos sus metadatos
let metadatos = entrada.metadata()?;
imprimir_detalles(entrada.file_name(), metadatos);
}
}
}
Ok(())
}
/// Imprime información detallada de un archivo
fn imprimir_detalles(nombre: std::ffi::OsString, metadatos: fs::Metadata) {
let tamano = metadatos.len();
// La fecha de modificación puede fallar, usamos UNIX_EPOCH como fallback
let modificacion = metadatos.modified().unwrap_or(SystemTime::UNIX_EPOCH);
println!(
" [Archivo] Nombre: {:?} | Tamaño: {} bytes | Modificado: {:?}",
nombre, tamano, modificacion
);
}
fn main() -> io::Result<()> {
// Definimos el punto de partida usando PathBuf para asegurar propiedad de los datos
let punto_partida = PathBuf::from(".");
// canonicalize resuelve rutas relativas y enlaces simbólicos a rutas absolutas
let ruta_absoluta = punto_partida.canonicalize()?;
println!("Iniciando recorrido en: {:?}", ruta_absoluta);
recorrer_recursivo(&ruta_absoluta)?;
Ok(())
}
Explicación del Código
En la función recorrer_recursivo, el parámetro ruta es de tipo &Path, una referencia que nos permite inspeccionar la ruta sin tomar posesión de ella. Usamos ruta.is_dir() para validar si debemos entrar en el directorio.
Dentro del bucle for, fs::read_dir(ruta)? nos entrega un iterador. Cada entrada es un DirEntry. Llamamos a entrada.path() para obtener un PathBuf que combine la ruta base con el nombre del elemento actual, lo cual es necesario para la recursividad. El método entrada.file_type()? es clave para la eficiencia, ya que nos permite decidir si entrar en un subdirectorio sin cargar todos los metadatos pesados de inmediato.
Cuando tipo.is_dir() es verdadero, la función recorrer_recursivo(&path) se invoca con la nueva ruta, permitiendo la navegación profunda. Si es un archivo, llamamos a imprimir_detalles.
En imprimir_detalles, el parámetro nombre es un OsString (ya que los nombres de archivos no siempre son UTF-8 válidos). Usamos metadatos.len() para obtener el tamaño en bytes y metadatos.modified() para obtener la SystemTime de la última modificación.
En main, creamos un punto_partida como PathBuf. El método canonicalize() es fundamental para transformar el punto actual . en una ruta absoluta limpia, evitando confusiones con rutas relativas durante el recorrido.
N° 91