Avatar ·

Как получить список файлов в директории на Rust?

Всем привет! Я только начал изучать Rust и пытаюсь написать простую утилиту для работы с файлами. Мне нужно получить список всех файлов в заданной директории. На Python я бы использовал `os.listdir()`, но в Rust я не могу найти аналог. Пробовал использовать `std::fs::read_dir()`, но не понимаю, как правильно обработать результат. Вот мой код: ```rust use std::fs; fn main() { let entries = fs::read_dir(".").unwrap(); for entry in entries { println!("{:?}", entry); } } ``` Он выводит что-то вроде `Ok(DirEntry(...))`, но мне нужны просто имена файлов. Ещё я хочу отличать файлы от папок и, возможно, фильтровать только файлы с определённым расширением (например, `.txt`). Также интересует, как обрабатывать ошибки без `unwrap()`, потому что в реальном проекте папка может не существовать или не быть доступной для чтения. Подскажите, как правильно реализовать получение списка файлов в Rust? Желательно с обработкой ошибок и фильтрацией. Заранее спасибо! <<>>

Правильный способ получения списка файлов в Rust

Вы почти на правильном пути! std::fs::read_dir — это именно то, что нужно. Проблема в том, что он возвращает итератор по Result, и вам нужно извлечь из каждого элемента имя файла. Вот полное решение с обработкой ошибок и фильтрацией:

use std::fs;
use std::path::Path;

fn list_files(dir: &Path) -> Result<Vec<String>, std::io::Error> {
    let mut files = Vec::new();
    
    let entries = fs::read_dir(dir)?;
    
    for entry in entries {
        let entry = entry?; // обрабатываем ошибку доступа к конкретному файлу
        let path = entry.path();
        
        // Проверяем, что это файл, а не директория
        if path.is_file() {
            // Извлекаем имя файла с расширением
            if let Some(filename) = path.file_name() {
                if let Some(name) = filename.to_str() {
                    files.push(name.to_string());
                }
            }
        }
    }
    
    Ok(files)
}

fn main() -> Result<(), std::io::Error> {
    let files = list_files(Path::new("."))?;
    
    println!("Файлы в текущей директории:");
    for file in &files {
        println!("- {}", file);
    }
    
    Ok(())
}

Фильтрация по расширению

Если нужно отфильтровать только файлы с определённым расширением (например, .txt):

use std::path::Path;

fn list_txt_files(dir: &Path) -> Result<Vec<String>, std::io::Error> {
    let mut files = Vec::new();
    
    let entries = fs::read_dir(dir)?;
    
    for entry in entries {
        let entry = entry?;
        let path = entry.path();
        
        if path.is_file() {
            // Проверяем расширение
            if let Some(ext) = path.extension() {
                if ext == "txt" {
                    if let Some(filename) = path.file_name() {
                        if let Some(name) = filename.to_str() {
                            files.push(name.to_string());
                        }
                    }
                }
            }
        }
    }
    
    Ok(files)
}

Рекурсивный обход поддиректорий

Если нужно получить все файлы включая подпапки, добавьте рекурсию:

fn list_files_recursive(dir: &Path) -> Result<Vec<String>, std::io::Error> {
    let mut files = Vec::new();
    
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            
            if path.is_dir() {
                // Рекурсивно обходим поддиректорию
                let mut sub_files = list_files_recursive(&path)?;
                files.append(&mut sub_files);
            } else if path.is_file() {
                if let Some(filename) = path.file_name() {
                    if let Some(name) = filename.to_str() {
                        files.push(name.to_string());
                    }
                }
            }
        }
    }
    
    Ok(files)
}

Ключевые моменты

  • Оператор ? — заменяет unwrap() и пробрасывает ошибку наверх, если она возникла. Используйте его в функциях, возвращающих Result.
  • Двойная обработка ошибок: сначала fs::read_dir может вернуть ошибку (если директория не существует), затем каждый entry может быть ошибкой (если файл недоступен).
  • Метод path() возвращает полный путь к файлу. Используйте file_name() для получения только имени.
  • Метод is_file() и is_dir() позволяют отличить файлы от папок.
  • Метод extension() возвращает расширение файла (без точки).

Также можно использовать более идиоматичный подход с цепочками итераторов:

fn list_files_functional(dir: &Path) -> Result<Vec<String>, std::io::Error> {
    fs::read_dir(dir)?
        .filter_map(|entry| entry.ok()) // игнорируем ошибки доступа
        .filter(|entry| entry.path().is_file())
        .filter(|entry| entry.path().extension() == Some(std::ffi::OsStr::new("txt")))
        .filter_map(|entry| {
            entry.path()
                .file_name()
                .and_then(|name| name.to_str().map(|s| s.to_string()))
        })
        .collect()
}

Этот вариант более компактный, но менее понятный для новичков. Выбирайте тот, который вам удобнее.

<<>>

Альтернативный подход с использованием стороннего крейта

Если вы часто работаете с файловой системой, рекомендую обратить внимание на крейт walkdir. Он предоставляет более удобный API для обхода директорий, включая рекурсивный обход, фильтрацию и обработку ошибок.

Добавьте в Cargo.toml:

[dependencies]
walkdir = "2"

Пример использования:

use walkdir::WalkDir;

fn main() {
    for entry in WalkDir::new(".") {
        match entry {
            Ok(entry) => {
                if entry.file_type().is_file() {
                    println!("{}", entry.file_name().to_string_lossy());
                }
            }
            Err(e) => eprintln!("Ошибка: {}", e),
        }
    }
}

Преимущества:

  • Автоматический рекурсивный обход (можно отключить через .max_depth(1)).
  • Более читаемый код.
  • Встроенная обработка ошибок с возможностью продолжить обход при ошибке доступа к отдельному файлу.
  • Поддержка фильтрации по типу файла, имени, размеру и т.д.

Недостаток: дополнительная зависимость. Если вам нужен только простой список файлов, std::fs::read_dir из стандартной библиотеки — лучший выбор.

Также для простых случаев можно использовать std::fs в функциональном стиле:

use std::fs;

fn main() -> std::io::Result<()> {
    let files: Vec<_> = fs::read_dir(".")?
        .filter_map(|e| e.ok())
        .filter(|e| e.path().is_file())
        .map(|e| e.file_name().to_string_lossy().to_string())
        .collect();
    
    println!("{:?}", files);
    Ok(())
}