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.