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() -> u64
is_dir() -> bool
is_file() -> bool
is_symlink() -> bool
symlink_metadata
(см. ниже).file_type() -> FileType
FileType
, который можно дополнительно анализировать (например, с помощью is_dir()
, is_file()
).permissions() -> Permissions
Permissions
.modified() -> io::Result
accessed() -> io::Result
created() -> 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
, чтобы расширить ваш арсенал.
Попробуйте выполнить упражнение или переходите к следующему разделу!