How does glob::glob_with differ from glob for configuring matching behavior with MatchOptions?
glob provides simple pattern matching with default options, while glob_with accepts a MatchOptions struct that configures case sensitivity, pattern literal behavior, and backslash handling for cross-platform compatibility. The MatchOptions parameters let you control whether ? and * match path separators, whether matching is case-sensitive, and whether backslashes are treated literally or as escape charactersâessential for writing portable code or implementing custom matching semantics.
Basic Glob Usage
use glob::glob;
fn basic_glob() {
// The glob function uses default MatchOptions
// Default: case-sensitive on Unix, case-insensitive on Windows
// Default: ? and * don't match path separators
for entry in glob("src/**/*.rs").expect("Failed to read glob pattern") {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Error: {:?}", e),
}
}
}glob provides simple pattern matching with platform-appropriate defaults.
MatchOptions Configuration
use glob::{glob_with, MatchOptions};
fn match_options_structure() {
// MatchOptions controls glob behavior
let options = MatchOptions {
case_sensitive: true, // Match case exactly
require_literal_separator: true, // * and ? don't match /
require_literal_leading_dot: true, // * and ? don't match leading .
};
// Use glob_with to apply custom options
for entry in glob_with("src/**/*.rs", options).expect("Invalid pattern") {
match entry {
Ok(path) => println!("Found: {:?}", path),
Err(e) => println!("Error: {:?}", e),
}
}
}MatchOptions configures three key behaviors: case sensitivity, separator matching, and leading dot handling.
Case Sensitivity Control
use glob::{glob_with, MatchOptions};
fn case_sensitivity() {
// Default on Unix: case-sensitive
// Default on Windows: case-insensitive
// Case-insensitive matching
let case_insensitive = MatchOptions {
case_sensitive: false,
require_literal_separator: true,
require_literal_leading_dot: false,
};
// On Unix, this matches "README.md", "readme.md", "ReadMe.MD", etc.
for entry in glob_with("readme.md", case_insensitive).unwrap() {
if let Ok(path) = entry {
println!("Matched: {:?}", path);
}
}
// Case-sensitive matching (force on all platforms)
let case_sensitive = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
// Only matches exactly "README.md", not "readme.md"
for entry in glob_with("README.md", case_sensitive).unwrap() {
if let Ok(path) = entry {
println!("Exact match: {:?}", path);
}
}
}case_sensitive controls whether pattern matching distinguishes between uppercase and lowercase.
Platform Differences
use glob::{glob, glob_with, MatchOptions};
fn platform_differences() {
// On Unix: glob uses case_sensitive = true
// On Windows: glob uses case_sensitive = false
// glob() uses platform defaults
// These will behave differently across platforms:
for entry in glob("*.TXT").unwrap() {
// Unix: only matches .TXT exactly
// Windows: matches .txt, .TXT, .Txt, etc.
}
// For consistent cross-platform behavior, use glob_with
let consistent_options = MatchOptions {
case_sensitive: false, // Consistent case-insensitive on all platforms
require_literal_separator: true,
require_literal_leading_dot: false,
};
for entry in glob_with("*.txt", consistent_options).unwrap() {
// Now behaves identically on Unix and Windows
}
}glob uses platform-specific defaults; glob_with enables consistent behavior.
Separator Matching with require_literal_separator
use glob::{glob_with, MatchOptions};
fn separator_matching() {
// require_literal_separator controls whether * and ? match path separators
// Default: require_literal_separator = true
// * does NOT match / (or \ on Windows)
let default_options = MatchOptions::new();
// Pattern: "src/*"
// Matches: "src/main.rs", "src/lib.rs"
// Does NOT match: "src/utils/mod.rs" (because * doesn't match /)
// With require_literal_separator = false:
let match_separators = MatchOptions {
case_sensitive: true,
require_literal_separator: false, // * and ? can match /
require_literal_leading_dot: false,
};
// Pattern: "src/*"
// Now matches: "src/main.rs", "src/utils/mod.rs"
// Because * can match the path separator /
for entry in glob_with("src/*", match_separators).unwrap() {
if let Ok(path) = entry {
println!("Deep match: {:?}", path);
}
}
}require_literal_separator controls whether wildcards can match path separators.
Leading Dot Handling with require_literal_leading_dot
use glob::{glob_with, MatchOptions};
fn leading_dot_handling() {
// require_literal_leading_dot controls whether * and ? match leading dots
// Default: require_literal_leading_dot = false
// * and ? CAN match leading dots (like .gitignore)
// Pattern: "*.txt"
// Matches: "file.txt", ".hidden.txt"
// With require_literal_leading_dot = true:
let no_leading_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true, // * and ? don't match leading .
};
// Pattern: "*.txt"
// Now matches: "file.txt"
// Does NOT match: ".hidden.txt" (leading dot requires literal match)
// This is useful for Unix-style hidden file conventions
for entry in glob_with("*.txt", no_leading_dot).unwrap() {
if let Ok(path) = entry {
println!("Visible file: {:?}", path);
}
}
}require_literal_leading_dot controls whether wildcards can match leading dots in filenames.
Hidden Files on Unix
use glob::{glob_with, MatchOptions};
fn unix_hidden_files() {
// On Unix, files starting with . are "hidden"
// To exclude hidden files by default (like shell globs):
let exclude_hidden = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true, // Require literal . at start
};
// Pattern: "*"
// Matches: "file.txt", "dir"
// Does NOT match: ".hidden", ".gitignore"
// Pattern: ".*"
// Matches: ".hidden", ".gitignore"
// Does NOT match: "file.txt"
// To include hidden files:
let include_hidden = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
// Pattern: "*"
// Now matches: "file.txt", ".hidden", ".gitignore"
}require_literal_leading_dot: true implements Unix hidden file conventions.
Matching Default Options
use glob::MatchOptions;
fn default_options() {
// MatchOptions::new() provides defaults
let defaults = MatchOptions::new();
// These defaults are:
// - case_sensitive: true on Unix, false on Windows
// - require_literal_separator: true (wildcards don't match /)
// - require_literal_leading_dot: false (wildcards match leading .)
// This matches what glob() uses
// You can modify selectively:
let case_insensitive = MatchOptions {
case_sensitive: false,
..MatchOptions::new()
};
}MatchOptions::new() provides sensible defaults matching glob behavior.
Cross-Platform Consistency
use glob::{glob_with, MatchOptions};
fn cross_platform_consistency() {
// For consistent behavior across platforms:
let consistent = MatchOptions {
case_sensitive: true, // Force case-sensitive everywhere
require_literal_separator: true, // Force separator behavior
require_literal_leading_dot: false, // Include hidden files
};
// This behaves identically on Unix, Windows, macOS
for entry in glob_with("**/*.rs", consistent).unwrap() {
if let Ok(path) = entry {
println!("Rust file: {:?}", path);
}
}
}Explicit MatchOptions ensures consistent behavior regardless of platform.
Combining Options for Shell-Like Behavior
use glob::{glob_with, MatchOptions};
fn shell_like_behavior() {
// Unix shell-like behavior
let shell_options = MatchOptions {
case_sensitive: true,
require_literal_separator: true, // * doesn't match / in shell
require_literal_leading_dot: true, // * doesn't match . files in shell
};
// Pattern: "*.txt"
// Matches: "file.txt"
// Does NOT match: ".hidden.txt" (shell-like hidden file behavior)
// Does NOT match: "subdir/file.txt" (* doesn't cross directories)
for entry in glob_with("*.txt", shell_options).unwrap() {
if let Ok(path) = entry {
println!("Shell-like match: {:?}", path);
}
}
}require_literal_leading_dot: true combined with require_literal_separator: true mimics shell behavior.
Recursive Patterns
use glob::{glob_with, MatchOptions};
fn recursive_patterns() {
// ** pattern matches recursively (always, regardless of options)
let options = MatchOptions {
case_sensitive: true,
require_literal_separator: true, // Doesn't affect **
require_literal_leading_dot: false,
};
// **/ matches any directory depth
for entry in glob_with("src/**/*.rs", options).unwrap() {
if let Ok(path) = entry {
println!("Rust source: {:?}", path);
}
}
// Note: ** is special and always crosses directory boundaries
// require_literal_separator doesn't affect it
}The ** pattern always matches recursively regardless of require_literal_separator.
Pattern with Backslash
use glob::{glob_with, MatchOptions};
fn backslash_handling() {
// On Windows, \ is a path separator
// On Unix, \ is typically an escape character or literal
// MatchOptions doesn't directly control backslash handling
// But it affects separator matching
// Pattern with forward slash works on all platforms:
for entry in glob_with("src/**/*.rs", MatchOptions::new()).unwrap() {
// Works on Unix and Windows
}
// For cross-platform code, always use forward slashes in patterns
// The glob crate handles conversion internally on Windows
}Use forward slashes in glob patterns for cross-platform compatibility.
Error Handling Differences
use glob::{glob, glob_with, MatchOptions, PatternError};
fn error_handling() {
// Both functions return Result<Paths, PatternError>
// Invalid pattern
let result = glob("[invalid"); // Unclosed character class
match result {
Ok(_paths) => {}
Err(e) => {
println!("Pattern error at position {}: {}", e.pos, e.msg);
}
}
// Same with glob_with
let result = glob_with("[invalid", MatchOptions::new());
match result {
Ok(_paths) => {}
Err(e) => {
println!("Pattern error: {}", e);
}
}
}Both functions handle pattern errors the same way; options don't affect error handling.
Performance Considerations
use glob::{glob_with, MatchOptions};
fn performance() {
// Case-insensitive matching may be slightly slower
// because it needs to check both cases
// require_literal_separator = false may match more paths
// potentially affecting performance on large directory trees
let fast_options = MatchOptions {
case_sensitive: true, // Faster: exact case match
require_literal_separator: true, // Faster: * stops at /
require_literal_leading_dot: false,
};
// If you know your patterns and file system:
// - Use case_sensitive = true on Unix for speed
// - Use require_literal_separator = true to limit depth
for entry in glob_with("**/*.rs", fast_options).unwrap() {
// More efficient with case_sensitive = true
}
}Case-sensitive matching can be faster; separator restrictions limit search depth.
Practical Example: Finding Source Files
use glob::{glob_with, MatchOptions};
fn find_source_files() {
// Find all Rust source files, excluding hidden directories
let options = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true, // Skip .git, .cargo, etc.
};
for entry in glob_with("**/*.rs", options).unwrap() {
match entry {
Ok(path) => {
// Won't match files in .git/, .cargo/, etc.
println!("Source file: {}", path.display());
}
Err(e) => println!("Error accessing path: {}", e),
}
}
}require_literal_leading_dot: true excludes hidden files and directories.
Practical Example: Case-Insensitive Config Search
use glob::{glob_with, MatchOptions};
fn find_config_files() {
// Find config files with case-insensitive matching
// Useful for user-specified patterns
let options = MatchOptions {
case_sensitive: false, // Match README.md, readme.md, Readme.Md
require_literal_separator: true,
require_literal_leading_dot: false,
};
// User might type "readme" and we want to find README.md
for entry in glob_with("readme*", options).unwrap() {
match entry {
Ok(path) => println!("Found readme: {}", path.display()),
Err(e) => println!("Error: {}", e),
}
}
}Case-insensitive matching is useful for user input patterns.
Comparing glob vs glob_with
use glob::{glob, glob_with, MatchOptions};
fn comparison() {
// glob() - simple, platform defaults
// Use when:
// - Platform-specific behavior is acceptable
// - Default options match your needs
// - Quick scripts or one-offs
for entry in glob("*.rs").unwrap() {
// Uses platform defaults for case sensitivity
}
// glob_with() - configurable, consistent behavior
// Use when:
// - Cross-platform consistency needed
// - Need to control hidden file matching
// - Need case-sensitive matching on Windows
// - Need case-insensitive matching on Unix
let options = MatchOptions {
case_sensitive: false, // Consistent behavior across platforms
require_literal_separator: true,
require_literal_leading_dot: true,
};
for entry in glob_with("*.rs", options).unwrap() {
// Uses specified options
}
}Use glob for platform defaults; use glob_with for controlled behavior.
Options Summary Table
use glob::MatchOptions;
fn options_table() {
// | Option | true | false |
// |--------|------|-------|
// | case_sensitive | Distinguish case (Unix default) | Ignore case (Windows default) |
// | require_literal_separator | * and ? don't match / (default) | * and ? can match / |
// | require_literal_leading_dot | * and ? don't match leading . | * and ? match leading . (default) |
// Common configurations:
// 1. Unix shell-like behavior
let unix_shell = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
};
// 2. Windows-like behavior (on all platforms)
let windows_like = MatchOptions {
case_sensitive: false,
require_literal_separator: true,
require_literal_leading_dot: false,
};
// 3. Permissive matching
let permissive = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
}Synthesis
Quick reference:
use glob::{glob, glob_with, MatchOptions};
fn quick_reference() {
// glob() - default options
for entry in glob("*.rs").unwrap() {
// Platform-specific defaults
}
// glob_with() - custom options
let options = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
};
for entry in glob_with("*.rs", options).unwrap() {
// Controlled behavior
}
// Key differences:
// - glob uses platform defaults (case-sensitive on Unix, not on Windows)
// - glob_with lets you override each behavior
// - Both return an iterator of paths
// - Both handle pattern errors the same way
}Key insight: The difference between glob and glob_with is entirely about control over matching behavior through MatchOptions. The glob function uses platform-appropriate defaults: case-sensitive on Unix, case-insensitive on Windows, with wildcards not matching path separators but matching leading dots. glob_with exposes three configuration knobs: case_sensitive controls whether the pattern distinguishes between uppercase and lowercase letters; require_literal_separator controls whether * and ? can match path separators (setting this to false allows * to match /, enabling patterns like src/* to match src/utils/mod.rs); and require_literal_leading_dot controls whether wildcards can match leading dots in filenames (setting this to true implements Unix-style hidden file exclusion). For cross-platform code, explicit MatchOptions ensures consistent behavior regardless of where the code runs. The most common configurations are require_literal_leading_dot: true for shell-like hidden file handling and case_sensitive: false for user-facing pattern matching that should work regardless of case input.
