Loading pageā¦
Rust walkthroughs
Loading pageā¦
glob::glob_with and glob for pattern matching options?glob::glob_with accepts a MatchOptions struct that controls pattern matching behavior, while glob uses default matching options that prioritize simplicity but may not suit all use cases. The key difference is configurability: glob_with enables case-insensitive matching on Unix systems, control over symlink following, and toggling backslash escapingābehaviors that glob hardcodes to specific defaults. The trade-off is between the simpler API of glob (single function call with sensible defaults) and the flexibility of glob_with (explicit configuration for cross-platform consistency or specialized matching requirements). Use glob for quick scripts with standard patterns; use glob_with when you need predictable cross-platform behavior or specific matching semantics.
use glob::{glob, glob_with, MatchOptions};
fn main() {
// glob: simple function with default options
for entry in glob("src/**/*.rs").unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Error: {:?}", e),
}
}
// glob_with: same pattern with explicit options
let options = MatchOptions::new();
for entry in glob_with("src/**/*.rs", options).unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Error: {:?}", e),
}
}
}glob uses default MatchOptions; glob_with accepts explicit configuration.
use glob::{glob_with, MatchOptions};
fn main() {
let options = MatchOptions {
case_sensitive: true, // Match case exactly
require_literal_separator: false, // * and ? can match /
require_literal_leading_dot: false, // * and ? can match leading dot
};
// Each field controls a specific matching behavior
for entry in glob_with("src/**/*.rs", options).unwrap() {
match entry {
Ok(path) => println!("Path: {:?}", path),
Err(e) => println!("Error: {:?}", e),
}
}
}MatchOptions has three boolean fields that control pattern matching behavior.
use glob::{glob, glob_with, MatchOptions};
fn main() {
// Default behavior differs by platform:
// - Unix: case-sensitive (file.txt != FILE.TXT)
// - Windows: case-insensitive (file.txt == FILE.TXT)
// On Unix, this won't match "FILE.TXT":
for entry in glob("*.txt").unwrap() {
if let Ok(path) = entry {
println!("glob found: {:?}", path);
}
}
// Force case-insensitive matching on all platforms
let case_insensitive = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
for entry in glob_with("*.txt", case_insensitive).unwrap() {
if let Ok(path) = entry {
println!("glob_with (insensitive) found: {:?}", path);
}
}
// Force case-sensitive matching on all platforms
let case_sensitive = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
};
for entry in glob_with("*.txt", case_sensitive).unwrap() {
if let Ok(path) = entry {
println!("glob_with (sensitive) found: {:?}", path);
}
}
}case_sensitive: false provides consistent cross-platform behavior.
use glob::{glob_with, MatchOptions};
fn main() {
// require_literal_separator controls whether * and ? can match /
// Default: false, * can match /
// Pattern "src/*.rs" matches "src/foo.rs" AND "src/subdir/foo.rs"
let wildcard_path = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
};
// require_literal_separator: true
// Pattern "src/*.rs" matches "src/foo.rs" but NOT "src/subdir/foo.rs"
let literal_sep = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
// Use require_literal_separator: true when you want
// * to NOT cross directory boundaries
for entry in glob_with("test_data/*.txt", literal_sep).unwrap() {
if let Ok(path) = entry {
println!("Literal sep: {:?}", path);
}
}
}require_literal_separator: true prevents wildcards from matching path separators.
use glob::{glob_with, MatchOptions};
fn main() {
// require_literal_leading_dot controls whether * and ? can match leading dots
// Default: false, * can match leading dot
// Pattern "*.txt" matches "file.txt" AND ".hidden.txt"
let wildcard_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
};
// require_literal_leading_dot: true
// Pattern "*.txt" matches "file.txt" but NOT ".hidden.txt"
// To match hidden files, pattern must start with literal dot: ".*.txt"
let literal_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: true,
};
// This mimics shell behavior where * doesn't match leading dot
// Shell: ls *.txt won't show .hidden.txt
for entry in glob_with("test_data/*.txt", literal_dot).unwrap() {
if let Ok(path) = entry {
println!("Literal dot: {:?}", path);
}
}
}require_literal_leading_dot: true mimics shell behavior for hidden files.
use glob::MatchOptions;
fn main() {
// MatchOptions::new() provides defaults that match glob()'s behavior
let default_options = MatchOptions::new();
// These are the defaults:
// case_sensitive: true on Unix, false on Windows
// require_literal_separator: false
// require_literal_leading_dot: false
// Platform-specific case sensitivity:
// - Unix: case_sensitive defaults to true
// - Windows: case_sensitive defaults to false
println!("case_sensitive: {}", default_options.case_sensitive);
println!("require_literal_separator: {}", default_options.require_literal_separator);
println!("require_literal_leading_dot: {}", default_options.require_literal_leading_dot);
}MatchOptions::new() provides platform-appropriate defaults matching glob() behavior.
use glob::{glob_with, MatchOptions};
fn find_files_cross_platform(pattern: &str) -> Vec<std::path::PathBuf> {
// Force consistent behavior across all platforms
let consistent_options = MatchOptions {
case_sensitive: false, // Case-insensitive everywhere
require_literal_separator: true, // * cannot match /
require_literal_leading_dot: true, // * cannot match leading dot
};
let mut results = Vec::new();
for entry in glob_with(pattern, consistent_options).unwrap() {
if let Ok(path) = entry {
results.push(path);
}
}
results
}
fn main() {
// Same behavior on Linux, macOS, and Windows
let files = find_files_cross_platform("src/**/*.rs");
for file in &files {
println!("Found: {:?}", file);
}
}glob_with with explicit options ensures consistent behavior across platforms.
use glob::{glob_with, MatchOptions};
fn glob_shell_like(pattern: &str) -> Vec<std::path::PathBuf> {
// Match typical shell behavior:
// - Case-sensitive on Unix
// - * and ? don't match leading dot (hidden files)
// - * and ? can match path separators (unless using **)
let shell_options = MatchOptions {
case_sensitive: cfg!(not(windows)), // Unix: true, Windows: false
require_literal_separator: false,
require_literal_leading_dot: true, // Shell behavior for hidden files
};
let mut results = Vec::new();
for entry in glob_with(pattern, shell_options).unwrap() {
if let Ok(path) = entry {
results.push(path);
}
}
results
}
fn main() {
let files = glob_shell_like("*.txt");
// Won't match .hidden.txt without explicit .* pattern
for file in &files {
println!("Shell-like found: {:?}", file);
}
}Configure MatchOptions to match specific shell behaviors.
use glob::{glob, glob_with, MatchOptions};
fn main() {
// Create test files for demonstration
// Assume we have: file.txt, .hidden.txt, another.txt
// Default glob with *.txt - matches hidden files depending on options
let include_hidden = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false, // * can match leading dot
};
// This matches both "file.txt" and ".hidden.txt"
println!("Including hidden files:");
for entry in glob_with("*.txt", include_hidden).unwrap() {
if let Ok(path) = entry {
println!(" {:?}", path);
}
}
// Shell-like: exclude hidden files unless explicitly matched
let exclude_hidden = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: true, // * cannot match leading dot
};
// This matches "file.txt" but NOT ".hidden.txt"
println!("\nExcluding hidden files:");
for entry in glob_with("*.txt", exclude_hidden).unwrap() {
if let Ok(path) = entry {
println!(" {:?}", path);
}
}
// To match hidden files with require_literal_leading_dot: true
// Use pattern ".*.txt" or ".*"
println!("\nHidden files explicitly:");
for entry in glob_with(".*", exclude_hidden).unwrap() {
if let Ok(path) = entry {
println!(" {:?}", path);
}
}
}Control whether wildcards match leading dots for hidden file handling.
use glob::{glob_with, MatchOptions};
fn main() {
// Control how deep wildcards traverse
// * in pattern: matches single directory level
// ** in pattern: matches any depth
// With require_literal_separator:
// - false: * can match / (acts like **)
// - true: * cannot match / (must use ** for recursive)
let single_level = MatchOptions {
case_sensitive: true,
require_literal_separator: true, // * stops at /
require_literal_leading_dot: false,
};
// Pattern "src/*.rs" matches "src/main.rs" but NOT "src/bin/cli.rs"
println!("Single level:");
for entry in glob_with("src/*.rs", single_level).unwrap() {
if let Ok(path) = entry {
println!(" {:?}", path);
}
}
// Pattern "src/**/*.rs" matches both "src/main.rs" AND "src/bin/cli.rs"
let recursive = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
println!("\nRecursive:");
for entry in glob_with("src/**/*.rs", recursive).unwrap() {
if let Ok(path) = entry {
println!(" {:?}", path);
}
}
}require_literal_separator affects how * interacts with path separators.
use glob::{glob, glob_with, MatchOptions};
fn main() {
// Both glob() and glob_with() follow symlinks by default
// when traversing directories (but not when matching the final component)
// This is controlled internally, not via MatchOptions
// If you need symlink control, consider walkdir crate
// Example: pattern "src/**/*.rs" will traverse into symlinked directories
let options = MatchOptions::new();
for entry in glob_with("src/**/*.rs", options).unwrap() {
if let Ok(path) = entry {
println!("Found: {:?}", path);
// If path is through a symlink, it's included
}
}
}Symlink following is not controlled by MatchOptions; both functions follow symlinks.
use glob::{glob, glob_with, MatchOptions};
fn main() {
// Both functions return GlobError for path access issues
// Both return Result iterator that yields Result<PathBuf, GlobError>
// Invalid pattern returns Result::Err immediately
match glob("**[invalid") {
Ok(_) => println!("Pattern valid"),
Err(e) => println!("Invalid pattern: {}", e),
}
// Path access errors during iteration yield Err
for entry in glob("/root/**/*").unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Access error: {:?}", e),
}
}
// glob_with has identical error handling
let options = MatchOptions::new();
for entry in glob_with("/root/**/*", options).unwrap() {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Access error: {:?}", e),
}
}
}Error handling is identical; only matching behavior differs.
use glob::{glob, glob_with, MatchOptions};
use std::time::Instant;
fn main() {
let pattern = "src/**/*.rs";
// Both functions have similar performance characteristics
// The difference is in matching semantics, not speed
// Default glob
let start = Instant::now();
let count = glob(pattern).unwrap().count();
println!("glob: {} files in {:?}", count, start.elapsed());
// glob_with with same options
let start = Instant::now();
let options = MatchOptions::new();
let count = glob_with(pattern, options).unwrap().count();
println!("glob_with: {} files in {:?}", count, start.elapsed());
// Performance differences come from matching complexity:
// - case_sensitive: false may have slight overhead
// - require_literal_separator: true may be faster (stops at /)
// - require_literal_leading_dot: true may be faster (skips hidden)
}Performance differences are negligible; choice should be based on semantics.
use glob::MatchOptions;
fn main() {
// All MatchOptions fields:
let options = MatchOptions {
// case_sensitive: Whether patterns match case-sensitively
// - true: "file.txt" only matches "file.txt", not "FILE.TXT"
// - false: "file.txt" matches "file.txt", "FILE.TXT", "File.Txt"
case_sensitive: true,
// require_literal_separator: Whether * and ? must match / literally
// - true: "src/*.rs" matches "src/main.rs" but not "src/bin/cli.rs"
// - false: "src/*.rs" matches both "src/main.rs" and "src/bin/cli.rs"
require_literal_separator: false,
// require_literal_leading_dot: Whether * and ? must match leading . literally
// - true: "*.txt" matches "file.txt" but not ".hidden.txt"
// - false: "*.txt" matches both "file.txt" and ".hidden.txt"
require_literal_leading_dot: false,
};
// Builder-style construction is not available, use struct literal
}Three boolean fields control all matching behavior variations.
use glob::{glob_with, MatchOptions};
fn main() {
// Use case 1: Cross-platform case-insensitive search
let case_insensitive = MatchOptions {
case_sensitive: false,
..MatchOptions::new()
};
// Use case 2: Shell-like behavior (hidden files excluded)
let shell_like = MatchOptions {
require_literal_leading_dot: true,
..MatchOptions::new()
};
// Use case 3: Non-recursive single directory
let single_dir = MatchOptions {
require_literal_separator: true,
..MatchOptions::new()
};
// Use case 4: Windows-style matching on all platforms
let windows_style = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
// Use case 5: Strict Unix-style matching
let unix_strict = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
};
}Common patterns emerge for specific matching requirements.
use glob::{glob_with, MatchOptions};
use std::path::PathBuf;
fn find_config_files(base_dir: &str) -> Vec<PathBuf> {
// Find config files: case-insensitive, exclude hidden
let options = MatchOptions {
case_sensitive: false, // Match "Config" and "config"
require_literal_separator: true, // Don't recurse into subdirs with *
require_literal_leading_dot: true, // Ignore hidden files
};
let mut configs = Vec::new();
// Common config file patterns
let patterns = [
"*.toml", // Cargo.toml, pyproject.toml
"*.yaml", // CI configs
"*.yml", // YAML configs
"*.json", // JSON configs
"*.config", // Generic config files
];
for pattern in &patterns {
let full_pattern = format!("{}/{}", base_dir, pattern);
for entry in glob_with(&full_pattern, options).unwrap() {
if let Ok(path) = entry {
configs.push(path);
}
}
}
configs
}
fn main() {
let configs = find_config_files(".");
for config in &configs {
println!("Config: {:?}", config);
}
}Use glob_with for predictable cross-platform configuration file discovery.
use glob::{glob_with, MatchOptions};
use std::path::PathBuf;
fn find_source_files(project_dir: &str, extensions: &[&str]) -> Vec<PathBuf> {
// Find source files: case-sensitive, include hidden .dotfiles
let options = MatchOptions {
case_sensitive: true, // Source files are case-sensitive
require_literal_separator: false, // Allow **/ patterns
require_literal_leading_dot: false, // Include .dotfiles
};
let mut sources = Vec::new();
for ext in extensions {
// Recursive search for all files with extension
let pattern = format!("{}/**/*.{}", project_dir, ext);
for entry in glob_with(&pattern, options).unwrap() {
if let Ok(path) = entry {
// Skip target/build directories
if path.to_string_lossy().contains("/target/") ||
path.to_string_lossy().contains("/build/") {
continue;
}
sources.push(path);
}
}
}
sources
}
fn main() {
let rust_files = find_source_files(".", &["rs"]);
println!("Found {} Rust files", rust_files.len());
let web_files = find_source_files(".", &["js", "ts", "tsx", "jsx"]);
println!("Found {} web files", web_files.len());
}Source code search typically needs recursive matching with case sensitivity.
MatchOptions fields:
| Field | true | false |
|-------|------|-------|
| case_sensitive | Exact case match | Case-insensitive |
| require_literal_separator | * cannot match / | * can match / |
| require_literal_leading_dot | * cannot match leading . | * can match leading . |
glob vs glob_with:
| Aspect | glob | glob_with |
|--------|--------|-------------|
| API | Single function call | Function + options struct |
| Defaults | Platform-specific | Explicit control |
| Cross-platform | Varies | Consistent when options set |
| Use case | Quick scripts | Controlled behavior |
Common configurations:
| Need | case_sensitive | require_literal_separator | require_literal_leading_dot |
|------|------------------|----------------------------|------------------------------|
| Shell-like | platform default | false | true |
| Case-insensitive | false | false | false |
| Strict Unix | true | true | true |
| Windows-style | false | false | false |
Key insight: glob::glob_with with explicit MatchOptions provides control over three matching behaviors that glob hardcodes to platform defaults: case sensitivity, wildcard matching of path separators, and wildcard matching of leading dots. The default glob function uses case_sensitive: true on Unix and false on Windows, allowing wildcards to match separators and leading dotsābehaviors that may not match shell semantics or cross-platform requirements. Use glob when platform-specific defaults are acceptable; use glob_with when you need case-insensitive matching on Unix, want to prevent wildcards from crossing directory boundaries, need to exclude hidden files from * patterns, or require consistent behavior across operating systems. The performance overhead is negligibleāthe choice should be based on matching semantics rather than speed. For production code that may run on multiple platforms, glob_with with explicit options ensures predictable behavior regardless of the host operating system.