std::io: Работа с вводом-выводом
2. std::fs: Операции с файловой системой
3. std::path: Управление путями к файлам
Практические советы
Пример: Комбинирование модулей
Упражнение
Заключение
В этом разделе мы погрузимся в три ключевых модуля стандартной библиотеки Rust, связанных с вводом-выводом и работой с файлами: std::io, std::fs и std::path. Эти модули являются основой для взаимодействия программы с внешним миром — будь то чтение пользовательского ввода, запись в файлы или управление путями. Лекция рассчитана на всех — от новичков, только начинающих свой путь в Rust, до опытных разработчиков, желающих углубить знания. Мы разберём каждый модуль с примерами, нюансами и практическими советами.
std::io: Работа с вводом-выводомМодуль std::io предоставляет инструменты для работы с вводом-выводом (I/O) в Rust. Это включает чтение из файлов, запись в них, работу со стандартными потоками (stdin, stdout, stderr) и низкоуровневые операции с потоками данных. Он построен вокруг концепции "потоков" — абстракции, которая позволяет читать или записывать байты последовательно.
Основные компоненты std::io:
Read, Write, Seek — определяют поведение потоков.File, Stdin, Stdout, BufReader, BufWriter — конкретные реализации потоков.io::Result, что требует обработки ошибок.Пример: чтение строки из стандартного ввода:
use std::io;
fn main() -> io::Result<()> {
let mut input = String::new();
println!("Введите что-нибудь:");
io::stdin().read_line(&mut input)?; // Читаем строку из stdin
println!("Вы ввели: {}", input.trim());
Ok(())
}
Комментарии:
io::stdin() возвращает объект Stdin, представляющий стандартный ввод.read_line записывает данные в String, включая символ новой строки (\n).trim() убирает лишние пробелы и \n для чистого вывода.? передаёт любую ошибку ввода-вывода вверх, а io::Result в main позволяет обработать её.Заметка: Для повышения производительности используйте BufReader или BufWriter вместо прямого чтения/записи. Буферизация снижает количество системных вызовов.
std::ioПример с буферизацией:
use std::io::{self, BufRead, BufReader};
use std::fs::File;
fn main() -> io::Result<()> {
let file = File::open("example.txt")?;
let reader = BufReader::new(file);
for line in reader.lines() {
println!("Строка: {}", line?);
}
Ok(())
}
Здесь BufReader оборачивает File, а метод lines() возвращает итератор по строкам, что удобно и эффективно.
std::fs: Операции с файловой системойМодуль std::fs предоставляет высокоуровневые функции для работы с файлами и директориями. Он построен поверх std::io и упрощает такие задачи, как создание, удаление, чтение и запись файлов.
Основные функции:
read_to_string: Читает файл в строку.write: Записывает данные в файл.create_dir, remove_dir: Управление директориями.File::open, File::create: Низкоуровневый доступ к файлам.Пример: запись и чтение файла:
use std::fs;
use std::io;
fn main() -> io::Result<()> {
// Записываем данные в файл
fs::write("output.txt", "Привет, Rust!")?;
// Читаем файл
let contents = fs::read_to_string("output.txt")?;
println!("Содержимое: {}", contents);
Ok(())
}
Комментарии:
fs::write перезаписывает файл, если он существует, или создаёт новый.fs::read_to_string удобен для текстовых файлов, но для больших файлов лучше использовать std::io с буферизацией.Предупреждение: Операции вроде fs::remove_file или fs::remove_dir_all необратимы. Всегда проверяйте пути перед удалением!
std::fs также позволяет получать информацию о файлах:
use std::fs;
fn main() -> std::io::Result<()> {
let metadata = fs::metadata("output.txt")?;
println!("Размер файла: {} байт", metadata.len());
println!("Это директория? {}", metadata.is_dir());
Ok(())
}
Метаданные включают размер, права доступа, время модификации и т.д.
Модуль std::fs предоставляет не только функции для чтения и записи файлов, но и мощный инструментарий для получения метаданных о файлах и директориях через структуру std::fs::Metadata. Метаданные — это информация о файле или директории, такая как размер, права доступа, время создания или модификации. Этот раздел подробно разберёт, как извлечь и использовать метаданные, с примерами и практическими советами.
Для получения метаданных используется функция fs::metadata, которая принимает путь к файлу или директории и возвращает io::Result. Если файл не существует или доступ к нему ограничен, функция вернёт ошибку.
Давайте начнём с базового примера, который вы уже видели:
use std::fs;
fn main() -> std::io::Result<()> {
let metadata = fs::metadata("output.txt")?;
println!("Размер файла: {} байт", metadata.len());
println!("Это директория? {}", metadata.is_dir());
Ok(())
}
Комментарии:
fs::metadata возвращает Result, который мы распаковываем с помощью ?.len() возвращает размер файла в байтах (тип u64).is_dir() проверяет, является ли объект директорией (возвращает bool).Но это только начало. Структура Metadata предоставляет гораздо больше информации. Давайте разберём все её методы и возможности.
std::fs::MetadataСтруктура Metadata содержит данные, зависящие от платформы, но Rust предоставляет унифицированный интерфейс для их извлечения. Вот полный список методов и их назначение:
len() -> u64is_dir() -> boolis_file() -> boolis_symlink() -> boolsymlink_metadata (см. ниже).file_type() -> FileTypeFileType, который можно дополнительно анализировать (например, с помощью is_dir(), is_file()).permissions() -> PermissionsPermissions.modified() -> io::Resultaccessed() -> io::Resultcreated() -> io::ResultДавайте напишем программу, которая извлекает и выводит все доступные метаданные:
use std::fs;
use std::io;
use std::time::SystemTime;
fn main() -> io::Result<()> {
let metadata = fs::metadata("output.txt")?;
// Основные свойства
println!("Размер: {} байт", metadata.len());
println!("Это файл? {}", metadata.is_file());
println!("Это директория? {}", metadata.is_dir());
// Права доступа
let perms = metadata.permissions();
println!("Только чтение? {}", perms.readonly());
// Временные метки
if let Ok(modified) = metadata.modified() {
println!("Последняя модификация: {:?}", modified);
}
if let Ok(accessed) = metadata.accessed() {
println!("Последний доступ: {:?}", accessed);
}
if let Ok(created) = metadata.created() {
println!("Создан: {:?}", created);
} else {
println!("Время создания недоступно");
}
Ok(())
}
Комментарии:
permissions() возвращает Permissions, где readonly() проверяет, доступен ли файл только для чтения.modified, accessed, created) возвращают io::Result, так как могут быть недоступны (например, created не поддерживается на некоторых файловых системах).if let для безопасной обработки возможных ошибок.metadata и symlink_metadataФункция fs::metadata следует за символическими ссылками и возвращает метаданные конечного файла. Если вам нужно получить метаданные самой ссылки, используйте fs::symlink_metadata:
use std::fs;
fn main() -> std::io::Result<()> {
let meta = fs::symlink_metadata("link.txt")?;
println!("Это символическая ссылка? {}", meta.is_symlink());
println!("Размер ссылки: {} байт", meta.len());
Ok(())
}
Комментарии:
is_symlink() работает только с symlink_metadata.len(), относится к самой ссылке, а не к файлу, на который она указывает.Метаданные зависят от операционной системы и файловой системы:
std::os::unix::fs::MetadataExt, такие как mode() (права в формате Unix), uid() (ID владельца), nlink() (число жёстких ссылок).std::os::windows::fs::MetadataExt, например, file_attributes().Пример для Unix:
#[cfg(target_os = "unix")]
use std::os::unix::fs::MetadataExt;
fn main() -> std::io::Result<()> {
let metadata = fs::metadata("output.txt")?;
#[cfg(target_os = "unix")]
{
println!("Режим прав: {:o}", metadata.mode()); // Восьмеричный формат
println!("ID владельца: {}", metadata.uid());
}
Ok(())
}
Здесь #[cfg] обеспечивает компиляцию кода только на Unix-системах.
Path::exists() перед вызовом metadata, чтобы избежать лишних ошибок.created() может быть недоступно (например, на ext4 в Linux). Планируйте запасной вариант.MetadataExt), либо используйте условную компиляцию.metadata требует системного запроса, так что не вызывайте его в цикле без необходимости.Программа, которая проверяет все файлы в директории и выводит их метаданные:
use std::fs;
use std::io;
fn main() -> io::Result<()> {
for entry in fs::read_dir(".")? {
let entry = entry?;
let path = entry.path();
let metadata = fs::metadata(&path)?;
println!("Путь: {:?}", path);
println!(" Размер: {} байт", metadata.len());
println!(" Тип: {}", if metadata.is_dir() { "директория" } else { "файл" });
if let Ok(modified) = metadata.modified() {
println!(" Модифицирован: {:?}", modified);
}
}
Ok(())
}
Комментарии:
fs::read_dir возвращает итератор по содержимому директории.DirEntry даёт доступ к пути и метаданным.Заметка: Метаданные кэшируются файловой системой, но могут быть устаревшими, если файл изменяется другой программой во время работы.
Предупреждение: Размер директории (len()) не всегда отражает суммарный размер её содержимого — это зависит от ОС.
std::path: Управление путями к файламМодуль std::path отвечает за работу с путями к файлам и директориям. Он абстрагирует различия между платформами (например, слэши в Windows и Unix) и предоставляет безопасный способ манипулировать путями.
Основные типы:
Path: Представление пути (не обязательно существующего).PathBuf: Владеющая версия Path, аналог String для путей.Пример: построение и анализ пути:
use std::path::{Path, PathBuf};
fn main() {
// Создаём путь
let path = Path::new("/usr/local/bin/rustc");
// Проверяем компоненты
println!("Расширение: {:?}", path.extension());
println!("Имя файла: {:?}", path.file_name());
println!("Существует? {}", path.exists());
// Строим новый путь
let mut path_buf = PathBuf::from("/tmp");
path_buf.push("test.txt");
println!("Новый путь: {:?}", path_buf);
}
Комментарии:
Path::new создаёт ссылку на путь без выделения памяти.PathBuf::push добавляет компонент к пути с учётом разделителей платформы.exists() проверяет наличие файла или директории.Заметка: Path не проверяет существование пути до вызова методов вроде exists() или использования с fs. Это просто абстракция.
match или ? для io::Result, чтобы не пропустить сбои.read_to_string ожидает UTF-8. Для других кодировок используйте fs::read и декодируйте вручную.PathBuf для динамических путей, чтобы избежать проблем с разделителями.BufReader) над read_to_string.Программа, которая читает файл по заданному пути и выводит статистику:
use std::fs;
use std::io;
use std::path::Path;
fn file_stats(path: &Path) -> io::Result<(usize, usize, usize)> {
let contents = fs::read_to_string(path)?;
let lines = contents.lines().count();
let words = contents.split_whitespace().count();
let chars = contents.chars().count();
Ok((lines, words, chars))
}
fn main() -> io::Result<()> {
let path = Path::new("example.txt");
if path.exists() {
let (lines, words, chars) = file_stats(path)?;
println!("Строк: {}, Слов: {}, Символов: {}", lines, words, chars);
} else {
println!("Файл {:?} не существует", path);
}
Ok(())
}
Здесь мы используем std::path для проверки пути, std::fs для чтения и std::io для обработки ошибок.
Напишите программу, которая:
std::env::args).std::path.std::fs и выводит первые 10 строк с использованием std::io::BufReader.Подсказка: Используйте take(10) с итератором lines() для ограничения вывода.
Модули std::io, std::fs и std::path — это фундаментальные инструменты для работы с вводом-выводом и файловой системой в Rust. Они обеспечивают безопасность, гибкость и кроссплатформенность, но требуют внимательного подхода к обработке ошибок и производительности. Освоив их, вы сможете решать широкий круг задач — от простых скриптов до сложных приложений. В следующих разделах мы рассмотрим другие модули, такие как std::collections, чтобы расширить ваш арсенал.
Попробуйте выполнить упражнение или переходите к следующему разделу!