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.

91