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 want or
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 is a regular file:
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
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(_) => ,
}
}
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: &) io::<> {
= fs::(path)?;
(metadata.())
}
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
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)? |
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
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.
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
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Can a Struct Extend Another Struct in Rust? Composition vs Inheritance
Learn how Rust handles struct reuse without inheritance, using composition, traits, and wrapper structs with practical examples.