Question
In Rust, how can I check whether a path exists and return a bool?
I found two possible approaches: using std::fs::PathExt or std::fs::metadata, and the documentation seems to suggest metadata as the more stable option.
Here is the code I started with:
use std::fs;
pub fn path_exists(path: &str) -> bool {
let metadata = try!(fs::metadata(path));
assert!(metadata.is_file());
}
The problem is that try!(fs::metadata(path)) still requires the function to return a Result<T, E>, but I only want this function to return a bool.
Why does try! behave this way, and what is the correct way to write a function that simply checks whether a path exists?
Short Answer
By the end of this page, you will understand why try! (and ?) only works in functions that return Result-like types, how to check whether a path exists in Rust, and when to use Path::exists() versus fs::metadata(). You will also see how to convert file-system errors into a simple bool when that is all you need.
Concept
In Rust, many file-system operations can fail. A path might not exist, permissions may be denied, or the path may be invalid. Because of that, APIs such as fs::metadata() return a Result instead of a plain value.
use std::fs;
let result = fs::metadata("file.txt");
That result is:
Ok(metadata)if Rust successfully read the file system metadataErr(error)if something went wrong
The try! macro was designed to make error propagation easier. If the result is Ok, it unwraps the value. If it is Err, it immediately returns that error from the current function.
That means try! only makes sense inside a function that itself returns a Result (or another compatible type). If your function returns bool, there is nowhere for the error to be propagated.
For a simple existence check, you usually do one of these:
- Use
Path::exists()when you only wanttrueorfalse - Use
fs::metadata()and convert theResultinto abool
Modern Rust most commonly uses std::path::Path:
use std::path::Path;
pub fn path_exists(path: &str) -> bool {
Path::new(path).exists()
}
This is simple and readable.
If you specifically want to use metadata(), you can write:
use std::fs;
pub fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}
This works because:
is_ok()returnstrueforOk(...)is_ok()returnsfalseforErr(...)
This converts the Result into the boolean you want.
Why this matters in real programming:
- Rust makes failures explicit instead of hiding them
- You decide whether to propagate the error, handle it, or convert it into a simpler value
- This leads to safer and more predictable code, especially for file and network operations
Mental Model
Think of Result as a delivery box.
Ok(value)means the box contains what you asked forErr(error)means the box contains a note explaining why delivery failed
try! is like saying: "If the box contains an error note, stop everything and send that note back to my caller."
That only works if your function is set up to send error notes back, which means it must return a Result.
A bool function is different. It only answers yes or no. It cannot carry an error note. So if you want only yes/no, you need to turn the Result into a boolean yourself.
For example:
fs::metadata(path)gives you the box.is_ok()asks: "Did I get a real value instead of an error?"Path::new(path).exists()asks the same question in a more direct way
Syntax and Examples
The two most common ways to check path existence in Rust are:
1. Using Path::exists()
use std::path::Path;
pub fn path_exists(path: &str) -> bool {
Path::new(path).exists()
}
This is the clearest option when you only need a boolean.
2. Using fs::metadata()
use std::fs;
pub fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}
This works because metadata() returns a Result, and is_ok() converts it to true or false.
Checking whether it is a file
If you want to know whether the path exists and is a regular file:
use std::fs;
pub fn is_file(path: &str) -> bool {
match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
}
}
Checking whether it is a directory
use std::fs;
pub fn is_dir(path: &str) -> bool {
match fs::metadata(path) {
Ok(metadata) => metadata.is_dir(),
Err(_) => false,
}
}
If you want to propagate errors instead
Then your function should return Result:
use std::fs;
use std::io;
pub fn path_is_file(path: &str) -> io::Result<bool> {
let metadata = fs::metadata(path)?;
Ok(metadata.is_file())
}
Here the ? operator is the modern replacement for try!.
Step by Step Execution
Consider this function:
use std::fs;
pub fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}
Now trace what happens for two inputs.
Case 1: The path exists
let exists = path_exists("Cargo.toml");
Step by step:
fs::metadata("Cargo.toml")runs- Rust asks the operating system for metadata about that path
- Suppose the file exists
- The function gets
Ok(metadata) .is_ok()is called onOk(metadata).is_ok()returnstruepath_existsreturnstrue
Case 2: The path does not exist
let exists = path_exists("missing.txt");
Step by step:
fs::metadata("missing.txt")runs- Rust asks the operating system for metadata
- The OS says the path does not exist
- The function gets
Err(error) .is_ok()is called onErr(error).is_ok()returnsfalsepath_existsreturnsfalse
Why your original version failed
Original code:
use std::fs;
pub fn path_exists(path: &str) -> bool {
let metadata = try!(fs::metadata(path));
assert!(metadata.is_file());
}
Problems:
try!wants to return early with an error ifmetadata()fails- But the function returns
bool, notResult assert!(metadata.is_file())does not return a boolean value for your functionassert!is mainly for testing/debugging; it returns()when it succeeds and panics when it fails
A corrected version for checking file existence would be:
use std::fs;
pub fn path_exists(path: &str) -> bool {
match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
}
}
Real World Use Cases
Checking whether a path exists is common in many Rust programs.
Configuration loading
Before reading a config file, an app may check whether it exists:
use std::path::Path;
if Path::new("config.toml").exists() {
println!("Config file found");
}
Creating files only when needed
A script may avoid overwriting an existing file:
use std::path::Path;
if !Path::new("report.txt").exists() {
println!("Safe to create report.txt");
}
Validating user input
A CLI tool may verify a file path provided by the user:
use std::fs;
fn input_file_valid(path: &str) -> bool {
match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
}
}
Checking directories before writing output
Programs often ensure an output directory exists before saving generated files.
Build tools and automation scripts
Rust scripts or developer tools commonly check for:
- source files
- environment-specific config files
- cache directories
- generated artifacts
Real Codebase Usage
In real projects, developers usually choose between a simple boolean check and full error handling based on the situation.
Pattern: simple existence check
When the exact error does not matter:
use std::path::Path;
if Path::new("settings.json").exists() {
// continue
}
This is common in scripts, setup code, and quick validation.
Pattern: guard clause
A function may return early if a required file is missing:
use std::path::Path;
fn load_if_present(path: &str) {
if !Path::new(path).exists() {
return;
}
println!("Loading {path}");
}
Pattern: keep the real error
Sometimes false is not enough. If the path fails because of permissions, you may want to know that.
use std::fs;
use std::io;
fn file_size(path: &str) -> io::Result<u64> {
let metadata = fs::metadata(path)?;
Ok(metadata.len())
}
Pattern: validation with match
Developers often inspect the metadata after successful access:
use std::fs;
fn validate_input(path: &str) -> bool {
match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
}
}
Pattern: avoid checking before acting when possible
In many codebases, developers skip a separate existence check and just try the operation directly.
use std::fs;
fn read_config(path: &str) -> String {
match fs::read_to_string(path) {
Ok(contents) => contents,
Err(_) => String::from("default config"),
}
}
This avoids a time-of-check/time-of-use issue where the file system can change between checking and using.
Common Mistakes
1. Using try! or ? in a function that returns bool
Broken example:
use std::fs;
fn path_exists(path: &str) -> bool {
let metadata = fs::metadata(path)?;
metadata.is_file()
}
Why it fails:
?can only return early from a function that returnsResult,Option, or a compatible type- A
boolfunction cannot carry that error
Fix:
use std::fs;
fn path_exists(path: &str) -> bool {
fs::metadata(path).is_ok()
}
2. Using assert! to produce a return value
Broken example:
assert!(metadata.is_file());
Why it fails:
assert!is not a boolean return expression- It panics if the condition is false
- It returns
()if the condition is true
Fix:
metadata.is_file()
3. Forgetting to return a value
Broken example:
fn path_exists(path: &str) -> bool {
let metadata = std::fs::metadata(path).is_ok();
}
Why it fails:
- The function body ends with
;, so the final expression becomes()
Fix:
fn path_exists(path: &str) -> bool {
std::fs::metadata(path).is_ok()
}
4. Confusing “exists” with “is a file”
A path may exist and still not be a file. It could be a directory.
use std::path::Path;
fn exists(path: &str) -> bool {
Path::new(path).exists()
}
If you need a file specifically, use metadata and is_file().
5. Hiding useful errors too early
Sometimes turning everything into false loses important information.
For example, these two cases both become false:
- file does not exist
- file exists but permission is denied
If that difference matters, return Result<bool, io::Error> instead.
Comparisons
| Approach | Returns | Best when | Notes |
|---|---|---|---|
Path::new(path).exists() | bool | You only want yes/no | Simple and readable |
fs::metadata(path) | Result<Metadata, io::Error> | You need file details | Lets you check is_file(), is_dir(), size, etc. |
fs::metadata(path).is_ok() | bool | You want existence using metadata | Converts success/failure into true/false |
fs::metadata(path)? | Metadata inside a Result-returning function | You want to propagate errors | Use when the caller should handle failure |
try! vs ?
| Feature | try! | ? |
|---|---|---|
| Style | Older macro | Modern operator |
| Use today | Rare | Preferred |
| Purpose | Early-return on error | Early-return on error |
Requires Result-like return type | Yes | Yes |
exists() vs is_file()
| Check | Meaning |
|---|---|
exists() | The path exists, regardless of type |
is_file() | The path exists and is a regular file |
is_dir() | The path exists and is a directory |
Cheat Sheet
use std::path::Path;
use std::fs;
Check whether a path exists
Path::new(path).exists()
Check whether metadata can be read
fs::metadata(path).is_ok()
Check whether a path is a file
match fs::metadata(path) {
Ok(metadata) => metadata.is_file(),
Err(_) => false,
}
Check whether a path is a directory
match fs::metadata(path) {
Ok(metadata) => metadata.is_dir(),
Err(_) => false,
}
Propagate errors instead of converting to bool
use std::io;
fn is_file(path: &str) -> io::Result<bool> {
let metadata = fs::metadata(path)?;
Ok(metadata.is_file())
}
Rules to remember
try!and?require aResult-like return typeassert!is for checking assumptions, not returning valuesexists()answers only yes/nometadata()gives more detail but returnsResult- If the specific error matters, keep
Resultinstead of collapsing tofalse
FAQ
Why does try! require a Result return type in Rust?
Because try! returns early when an error occurs. The function must therefore be able to return that error, which means it needs a Result-compatible return type.
What is the easiest way to check if a path exists in Rust?
Use:
use std::path::Path;
Path::new(path).exists()
How do I check if a path is a file and not just an existing path?
Use fs::metadata() and then call is_file() on the returned metadata.
Should I use try! or ? in modern Rust?
Use ?. It is the modern, cleaner replacement for try!.
Why is assert!(metadata.is_file()) not returning bool?
Because assert! is a macro for enforcing conditions. It panics if false and returns () if true. It does not return the boolean expression.
Is Path::exists() always better than fs::metadata()?
Not always. exists() is better for a simple yes/no check. metadata() is better when you need more information about the path.
Can a path exist but still not be readable?
Yes. A path may exist, but reading its metadata or contents may fail due to permissions or other OS-level errors.
Why might returning bool be too simple?
Because false hides the reason for failure. It does not tell you whether the file is missing, inaccessible, or invalid.
Mini Project
Description
Build a small Rust utility that checks a user-provided path and reports whether it exists, whether it is a file, and whether it is a directory. This demonstrates the difference between simple existence checks and reading metadata for more detailed validation.
Goal
Create a function and a small program that classifies a path as missing, a file, or a directory.
Requirements
- Read a path from a string variable in the program.
- Print whether the path exists.
- Print whether the path is a file.
- Print whether the path is a directory.
- Use
Pathandfs::metadata()appropriately.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.