Question

How to Check Whether a Path Exists in Rust

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 metadata
  • Err(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:

  1. Use Path::exists() when you only want true or false
  2. Use fs::metadata() and convert the Result into a bool

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() returns true for Ok(...)
  • is_ok() returns false for Err(...)

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 for
  • Err(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
Take Quiz

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:

  1. fs::metadata("Cargo.toml") runs
  2. Rust asks the operating system for metadata about that path
  3. Suppose the file exists
  4. The function gets Ok(metadata)
  5. .is_ok() is called on Ok(metadata)
  6. .is_ok() returns true
  7. path_exists returns true

Case 2: The path does not exist

let exists = path_exists("missing.txt");

Step by step:

  1. fs::metadata("missing.txt") runs
  2. Rust asks the operating system for metadata
  3. The OS says the path does not exist
  4. The function gets Err(error)
  5. .is_ok() is called on Err(error)
  6. .is_ok() returns false
  7. path_exists returns false

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:

  1. try! wants to return early with an error if metadata() fails
  2. But the function returns bool, not Result
  3. assert!(metadata.is_file()) does not return a boolean value for your function
  4. assert! 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
Take Quiz

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 returns Result, Option, or a compatible type
  • A bool function 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

ApproachReturnsBest whenNotes
Path::new(path).exists()boolYou only want yes/noSimple and readable
fs::metadata(path)Result<Metadata, io::Error>You need file detailsLets you check is_file(), is_dir(), size, etc.
fs::metadata(path).is_ok()boolYou want existence using metadataConverts success/failure into true/false
fs::metadata(path)?Metadata inside a Result-returning functionYou want to propagate errorsUse when the caller should handle failure

try! vs ?

Featuretry!?
StyleOlder macroModern operator
Use todayRarePreferred
PurposeEarly-return on errorEarly-return on error
Requires Result-like return typeYesYes

exists() vs is_file()

CheckMeaning
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
Take Quiz

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 a Result-like return type
  • assert! is for checking assumptions, not returning values
  • exists() answers only yes/no
  • metadata() gives more detail but returns Result
  • If the specific error matters, keep Result instead of collapsing to false

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.

Take Quiz

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 Path and fs::metadata() appropriately.
Take Quiz

Keep learning

Related questions

View more