Loading page…
Rust walkthroughs
Loading page…
glob::glob, how do you properly handle errors vs non-matching paths?The glob crate's glob() function returns an iterator that yields Result<PathBuf, GlobError>, where errors indicate problems reading directory contents rather than paths that don't match. Understanding this distinction is crucial for robust file system traversal code.
use glob::glob;
fn basic_usage() {
for entry in glob("src/**/*.rs").unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => eprintln!("Error: {:?}", e),
}
}
}The iterator yields Result values where errors represent I/O problems, not missing files.
use glob::{glob, GlobError, GlobResult};
fn understanding_errors() {
// Errors occur when:
// 1. Insufficient permissions to read a directory
// 2. Broken symbolic links
// 3. Directory was deleted during iteration
// 4. Other I/O errors
for result in glob("/root/**/*").unwrap() {
match result {
Ok(path) => println!("Path: {:?}", path),
Err(GlobError { path, error }) => {
// path: The path that caused the error
// error: The underlying io::Error
eprintln!("Failed to access {:?}: {}", path, error);
}
}
}
}A non-matching pattern simply yields an empty iterator, not an error.
use glob::glob;
fn non_matching_patterns() {
// Pattern with no matches yields empty iterator
let pattern = "nonexistent_directory/*.rs";
// glob() itself succeeds even if nothing matches
let count = glob(pattern).unwrap().count();
println!("Found {} matches", count); // "Found 0 matches"
// This is NOT an error - just no matches
for entry in glob("nonexistent/*.txt").unwrap() {
// This body never executes
println!("Found: {:?}", entry);
}
}Zero matches is a valid result, distinct from an error.
use glob::{glob, PatternError};
fn pattern_errors() {
// Invalid pattern syntax returns an error immediately
let result = glob("[invalid");
match result {
Err(PatternError { pos, msg }) => {
// Pattern syntax error
eprintln!("Invalid pattern at position {}: {}", pos, msg);
}
Ok(iter) => {
// Pattern is valid, iterate over matches
for entry in iter {
// Handle results
}
}
}
// Common pattern syntax errors:
// - Unmatched brackets: "[abc"
// - Invalid escape: "\\"
// - Empty alternatives: "{,a}"
}Pattern errors occur at pattern compilation time; I/O errors occur during iteration.
use glob::{glob, GlobError};
use std::path::PathBuf;
fn proper_handling() -> Result<Vec<PathBuf>, Vec<(PathBuf, std::io::Error)>> {
let mut matches = Vec::new();
let mut errors = Vec::new();
for entry in glob("src/**/*.rs").map_err(|e| vec![(PathBuf::new(), e.into())])? {
match entry {
Ok(path) => matches.push(path),
Err(GlobError { path, error }) => {
errors.push((path, error));
}
}
}
if errors.is_empty() {
Ok(matches)
} else {
Err(errors)
}
}
fn use_proper_handling() {
match proper_handling() {
Ok(paths) => {
println!("Found {} files", paths.len());
for path in paths {
println!(" {:?}", path);
}
}
Err(errors) => {
eprintln!("Encountered {} errors:", errors.len());
for (path, error) in errors {
eprintln!(" {:?}: {}", path, error);
}
}
}
}Collect both matches and errors separately for comprehensive handling.
use glob::{glob, GlobError};
use std::io;
fn permission_handling() {
for entry in glob("/var/log/**/*.log").unwrap() {
match entry {
Ok(path) => {
println!("Found log: {:?}", path);
// Process the file
}
Err(GlobError { path, error }) => {
match error.kind() {
io::ErrorKind::PermissionDenied => {
eprintln!("Permission denied: {:?}", path);
// Optionally skip and continue
}
io::ErrorKind::NotFound => {
// Path was deleted during iteration
eprintln!("Disappeared: {:?}", path);
}
_ => {
eprintln!("Error accessing {:?}: {}", path, error);
}
}
}
}
}
}Different error kinds may warrant different handling strategies.
use glob::{glob, GlobError};
fn continue_on_errors() {
let mut found = Vec::new();
let mut errors = Vec::new();
for entry in glob("/etc/**/*.conf").unwrap() {
match entry {
Ok(path) => found.push(path),
Err(e) => errors.push(e),
}
// Continue iterating even after errors
}
println!("Found {} files", found.len());
if !errors.is_empty() {
eprintln!("{} errors occurred", errors.len());
}
// Process found files
for path in found {
// ...
}
}Errors don't stop iteration; you can continue collecting matches.
use glob::GlobResult;
fn using_globresult() {
// GlobResult is a type alias for Result<PathBuf, GlobError>
fn process_entries(entries: impl Iterator<Item = GlobResult>) {
for result in entries {
match result {
Ok(path) => println!("Match: {:?}", path),
Err(e) => eprintln!("Error: {}", e),
}
}
}
process_entries(glob("*.rs").unwrap());
}GlobResult is the type you'll work with from the iterator.
use glob::glob;
fn filter_results() {
// Only collect successful matches
let matches: Vec<_> = glob("src/**/*.rs")
.unwrap()
.filter_map(Result::ok)
.collect();
println!("Found {} Rust files", matches.len());
// Or use filter for only errors
let errors: Vec<_> = glob("/root/**/*")
.unwrap()
.filter_map(Result::err)
.collect();
println!("{} errors occurred", errors.len());
}filter_map(Result::ok) is common when you want to ignore errors.
use glob::{glob, GlobError};
fn detailed_error_handling() {
for entry in glob("/var/**/*.log").unwrap() {
match entry {
Ok(path) => {
// Successfully matched path
println!("Matched: {:?}", path);
}
Err(GlobError { path, error }) => {
// Path exists but couldn't be accessed
eprintln!("Error on path {:?}", path);
eprintln!(" Error kind: {:?}", error.kind());
eprintln!(" Error message: {}", error);
// Get more context
if let Some(inner) = error.get_ref() {
eprintln!(" Inner error: {:?}", inner);
}
}
}
}
}GlobError provides both the problematic path and the underlying error.
use glob::glob;
fn no_matches() {
// These patterns might match nothing
let patterns = [
"nonexistent/**/*.rs",
"src/*.cpp", // No C++ files in src
"*.nonexistent", // No such extension
];
for pattern in patterns {
let matches: Vec<_> = glob(pattern)
.unwrap()
.filter_map(Result::ok)
.collect();
if matches.is_empty() {
println!("Pattern '{}' matched nothing", pattern);
} else {
println!("Pattern '{}' matched {} files", pattern, matches.len());
}
}
// This is different from a pattern error:
match glob("[") {
Ok(_) => println!("Pattern is valid"),
Err(e) => eprintln!("Invalid pattern: {}", e),
}
}Empty results and invalid patterns are distinct cases.
use glob::{glob, GlobError};
fn symlink_handling() {
// glob follows symlinks by default
for entry in glob("/usr/local/**/*.so").unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(GlobError { path, error }) => {
// Broken symlinks cause errors
eprintln!("Broken link or inaccessible: {:?}", path);
}
}
}
// Use glob_with to control symlink behavior
use glob::MatchOptions;
let options = MatchOptions {
follow_links: false, // Don't follow symlinks
..Default::default()
};
for entry in glob_with("/usr/local/**/*.so", options).unwrap() {
// Symlinks are returned as paths but not followed
match entry {
Ok(path) => println!("Path (possibly symlink): {:?}", path),
Err(e) => eprintln!("Error: {:?}", e),
}
}
}Broken symlinks cause GlobError, not missing matches.
use glob::{glob_with, MatchOptions};
fn match_options() {
let options = MatchOptions {
case_sensitive: false, // Case-insensitive matching
require_literal_separator: false, // * matches /
require_literal_leading_dot: false, // * matches .files
follow_links: true, // Follow symlinks
};
for entry in glob_with("**/*.RS", options).unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => eprintln!("Error: {}", e),
}
}
}Options control matching behavior and error conditions.
use glob::glob;
fn multiple_patterns() {
let patterns = ["src/**/*.rs", "tests/**/*.rs", "examples/**/*.rs"];
let mut all_rust_files = Vec::new();
let mut all_errors = Vec::new();
for pattern in patterns {
for entry in glob(pattern).unwrap() {
match entry {
Ok(path) => all_rust_files.push(path),
Err(e) => all_errors.push(e),
}
}
}
println!("Found {} Rust files", all_rust_files.len());
if !all_errors.is_empty() {
eprintln!("{} errors occurred", all_errors.len());
}
}Each pattern has its own potential for errors.
use glob::{glob, GlobError};
use std::io;
fn classify_errors() {
for entry in glob("/system/**/*.dat").unwrap() {
if let Err(GlobError { path, error }) = entry {
match error.kind() {
io::ErrorKind::PermissionDenied => {
// Skip silently or log
eprintln!("Skipping (no access): {:?}", path);
}
io::ErrorKind::NotFound => {
// Race condition - file deleted during glob
eprintln!("Disappeared: {:?}", path);
}
io::ErrorKind::Other => {
// Could be broken symlink
if error.to_string().contains("symlink") {
eprintln!("Broken symlink: {:?}", path);
} else {
eprintln!("Other error: {:?} - {}", path, error);
}
}
_ => {
eprintln!("Unexpected error: {:?} - {}", path, error);
}
}
}
}
}Different errors may need different responses.
use glob::{glob, GlobError};
use std::path::PathBuf;
use std::io;
#[derive(Debug)]
struct SearchResult {
matches: Vec<PathBuf>,
permission_denied: Vec<PathBuf>,
not_found: Vec<PathBuf>,
other_errors: Vec<(PathBuf, io::Error)>,
}
fn search_files(pattern: &str) -> SearchResult {
let mut result = SearchResult {
matches: Vec::new(),
permission_denied: Vec::new(),
not_found: Vec::new(),
other_errors: Vec::new(),
};
match glob(pattern) {
Ok(iter) => {
for entry in iter {
match entry {
Ok(path) => result.matches.push(path),
Err(GlobError { path, error }) => {
match error.kind() {
io::ErrorKind::PermissionDenied => {
result.permission_denied.push(path);
}
io::ErrorKind::NotFound => {
result.not_found.push(path);
}
_ => {
result.other_errors.push((path, error));
}
}
}
}
}
}
Err(e) => {
// Pattern syntax error
eprintln!("Invalid pattern: {}", e);
}
}
result
}
fn use_search() {
let result = search_files("/var/log/**/*.log");
println!("Found {} matches", result.matches.len());
if !result.permission_denied.is_empty() {
eprintln!("{} directories inaccessible", result.permission_denied.len());
}
if !result.other_errors.is_empty() {
eprintln!("{} other errors", result.other_errors.len());
}
}A complete solution categorizes all outcomes.
use glob::{glob, Pattern};
fn validate_pattern(pattern: &str) -> bool {
Pattern::new(pattern).is_ok()
}
fn check_patterns() {
let patterns = [
"src/**/*.rs", // Valid
"*.txt", // Valid
"[", // Invalid - unmatched bracket
"**/*.rs", // Valid
];
for pattern in patterns {
if validate_pattern(pattern) {
println!("Valid pattern: {}", pattern);
// Can safely call glob()
let matches: Vec<_> = glob(pattern)
.unwrap()
.filter_map(Result::ok)
.collect();
println!(" {} matches", matches.len());
} else {
eprintln!("Invalid pattern: {}", pattern);
}
}
}Validate patterns before using them to avoid syntax errors.
When using glob::glob, proper error handling distinguishes three cases:
Pattern syntax errors: Returned immediately from glob() as Err(PatternError). Example: invalid bracket syntax like "[".
Runtime I/O errors: Yielded as Err(GlobError) during iteration. These include:
No matches: Not an error. The iterator simply yields zero items. An empty result is valid.
use glob::{glob, GlobError};
fn robust_glob(pattern: &str) {
// Handle pattern error
let iter = match glob(pattern) {
Ok(iter) => iter,
Err(e) => {
eprintln!("Invalid pattern: {}", e);
return;
}
};
// Handle iteration results
for entry in iter {
match entry {
Ok(path) => {
// Successfully matched path
println!("Match: {:?}", path);
}
Err(GlobError { path, error }) => {
// Runtime error accessing path
eprintln!("Error at {:?}: {}", path, error);
}
}
}
// Empty iterator = no matches (not an error)
}Best practices:
Ok and Err from the iteratorfilter_map(Result::ok) only when you want to silently ignore errorsio::ErrorKind for appropriate responses