What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use Path::to_str() when you need verified Unicode, Path::to_string_lossy() when readable display text is more important than exact bytes, and PathBuf::into_string() when you own the buffer and can consume it. Rust paths are not guaranteed to be UTF-8, so a conversion can legitimately fail. If the path must remain lossless and OS-native, keep it as Path/PathBuf or use OsStr/OsString instead.

Choose the conversion that matches your data

A Rust path is an operating-system path, not automatically a Unicode string. The standard library offers several conversions with different ownership and error behavior.

Need API Result Important caveat
Borrow Unicode text path.to_str() Option<&str> None for a path that is not valid Unicode
Readable text for logs or messages path.to_string_lossy() Cow<str> Invalid sequences become U+FFFD replacement characters
Consume an owned buffer as Unicode path_buf.into_string() Result<String, PathBuf> Stable since Rust 1.98.0; the original buffer is returned on failure
Preserve the native path representation as_os_str() or into_os_string() &OsStr or OsString No Unicode conversion is attempted
Format for output path.display() A display formatter Formatting may be lossy; use Debug for escaped output

The Rust standard-library documentation describes to_str() as yielding a &str only when the path is valid Unicode (Path documentation). That distinction matters on platforms and filesystems that permit non-UTF-8 names.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Convert a borrowed &Path with to_str()

to_str() borrows the path and performs a checked conversion. It does not allocate and it never changes the path. Because the return type is Option<&str>, your code must decide what to do when the path is not Unicode.

use std::path::Path;

fn path_text(path: &Path) -> Option<&str> {
    path.to_str()
}

fn main() {
    let path = Path::new("foo.txt");

    match path.to_str() {
        Some(text) => println!("{text}"),
        None => eprintln!("path is not valid Unicode"),
    }
}

Return an error instead of silently dropping the path

For application logic, propagate the failure rather than replacing it with an empty string or calling unwrap() without an invariant. A small helper can turn the optional result into your own error type:

use std::path::Path;

fn require_unicode(path: &Path) -> Result<&str, String> {
    path.to_str().ok_or_else(|| {
        format!("path is not valid Unicode: {path:?}")
    })
}

fn main() {
    let path = Path::new("reports/output.txt");
    match require_unicode(path) {
        Ok(text) => println!("writing to {text}"),
        Err(error) => eprintln!("{error}"),
    }
}

Use unwrap() or expect() only when your program has an explicit guarantee that every path came from valid UTF-8 input. The standard library intentionally allows paths that do not satisfy that guarantee.

Get readable text with to_string_lossy()

When the purpose is a status line, diagnostic message, or log entry, replacement characters may be acceptable. to_string_lossy() returns a Cow<str>: it can borrow when the path is already valid Unicode and creates an owned string only when replacement is needed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
use std::path::Path;

fn main() {
    let path = Path::new("foo.txt");
    let text = path.to_string_lossy();
    println!("{text}");
}

Any invalid byte sequence is replaced by U+FFFD, the Unicode replacement character, as documented by Rust (Path::to_string_lossy). The output is therefore suitable for humans but not for reconstructing the original path. Do not use it as a database key, cache key, protocol field, or other supposedly reversible serialization.

If an API specifically requires an owned String, call .into_owned() on the returned Cow:

use std::path::Path;

fn display_name(path: &Path) -> String {
    path.to_string_lossy().into_owned()
}

fn main() {
    let path = Path::new("foo.txt");
    let text = display_name(path);
    println!("{text}");
}

Consume a PathBuf with into_string()

When you own a PathBuf and no longer need it as a path, into_string() transfers its contents into a String without borrowing. Its return type is Result<String, PathBuf>. If conversion fails, ownership of the original PathBuf comes back in the Err variant, so the path is not lost.

use std::path::PathBuf;

fn main() {
    let path_buf = PathBuf::from("foo.txt");

    match path_buf.into_string() {
        Ok(text) => println!("{text}"),
        Err(original_path) => {
            eprintln!("path is not valid Unicode: {original_path:?}");
        }
    }
}

The current PathBuf documentation marks this method as stable since Rust 1.98.0 (PathBuf documentation). Check your compiler version when maintaining a library that supports older toolchains.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the buffer on older compilers or when you still need it

If you cannot rely on Rust 1.98.0, or you want to retain the original buffer, borrow it first and clone only after a successful check:

use std::path::PathBuf;

fn main() {
    let path_buf = PathBuf::from("foo.txt");

    match path_buf.to_str() {
        Some(text) => {
            let owned_text = text.to_owned();
            println!("{owned_text}");
            // path_buf is still available here.
        }
        None => eprintln!("path is not valid Unicode: {path_buf:?}"),
    }
}

This pattern works on older Rust versions because it uses the long-standing checked conversion and makes the ownership decision explicit.

Preserve the operating-system path instead of forcing Unicode

If the next API accepts a path, pass a path. Converting to String first can introduce a failure or data loss that the consumer never needed.

use std::path::{Path, PathBuf};

fn main() {
    let path = Path::new("foo.txt");
    let borrowed_os_str = path.as_os_str();

    let path_buf = PathBuf::from("foo.txt");
    let owned_os_string = path_buf.into_os_string();

    let _ = (borrowed_os_str, owned_os_string);
}

as_os_str() provides a borrowed OsStr; into_os_string() consumes a PathBuf and returns an owned OsString. These types preserve the platform-native representation. The relationship between Path, PathBuf, and OS strings is also illustrated in Rust By Example. The OsString documentation covers the same checked and lossy conversion behavior when you eventually need text.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use display() only for formatting

display() returns a formatter that implements Display; it is convenient in println!, error messages, and other human-facing output.

use std::path::Path;

fn main() {
    let path = Path::new("foo.txt");
    println!("{}", path.display());
    println!("{:?}", path);
}

Display formatting may be lossy. If you need escaped output that makes unusual characters visible, the standard-library guidance is to use the path’s Debug representation, as in {:?}, rather than treating display() as a data-preserving conversion.

Practical decision rules

  • Text is required and invalid Unicode is an error: call to_str(), handle None, and return an error when appropriate.
  • Text is for people reading logs or messages: call to_string_lossy(); document that replacement characters can occur.
  • You own a PathBuf and want to consume it: use into_string() on Rust 1.98.0 or later and handle the returned PathBuf on failure.
  • The destination works with paths: keep Path/PathBuf or convert to OsStr/OsString.
  • You only need interpolation in a message: use display() or Debug, depending on whether escaped output is important.

Common mistakes and their fixes

Unwrapping to_str() everywhere

Symptom: a program panics on a path supplied by the user or filesystem. Cause: to_str() returned None. Fix: match on the option, propagate an error, or deliberately choose to_string_lossy() for display-only output.

Using lossy output as a real path

Symptom: a path logged and later reconstructed no longer identifies the original file. Cause: U+FFFD replaced invalid sequences. Fix: store and pass the original PathBuf or an OsString; reserve lossy text for humans.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Expecting into_string() to leave the buffer usable

Symptom: code tries to use a PathBuf after calling into_string(). Cause: the method consumes ownership. Fix: borrow with to_str() when the path is still needed, or handle the returned PathBuf in the error branch.

Compiler reports that into_string is unavailable

Cause: the project uses a Rust compiler older than the method’s stated 1.98.0 stabilization. Fix: use to_str() followed by to_owned(), or raise the project’s minimum supported Rust version.

Output looks different from the original spelling

Cause: display formatting can be lossy, and operating systems have different path conventions. Fix: keep the native path type for processing and use Debug when escaped diagnostics are needed.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Testing conversion behavior

Tests should cover both successful Unicode paths and the failure branch. Construct paths through the platform APIs rather than assuming every filename can be represented by a Rust String. Assert the behavior your application promises: rejection for strict text APIs, replacement for human-readable logs, or unchanged native data when using OsStr/OsString. This keeps the conversion policy visible instead of letting an accidental unwrap() define it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If you also need a clean screenshot of generated Rust documentation or another web page, ScreenshotNeo provides a single HTTP request instead of configuring a headless browser. Its capture process accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each of those steps can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.

For a direct call, see the ScreenshotNeo API documentation:

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

The same endpoint can be called from Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Or from Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get an API key.

Frequently Asked Questions

Should a library function accept a String or a Path?

Accept &Path (or another path-native type) when the value identifies a filesystem entry. Convert to text only at the boundary that explicitly requires Unicode, so callers with non-Unicode paths are not forced to lose information.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is a lossy string suitable for a user-facing error?

Yes, when the goal is readable context rather than a value that another component will parse or use to access the filesystem. Make the lossiness intentional and keep the original path available for the actual operation.

What is the minimum compiler version for PathBuf::into_string()?

The standard-library documentation lists PathBuf::into_string() as stable since Rust 1.98.0. Projects supporting earlier compilers should use checked to_str() plus to_owned() or retain an OS-native path type.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.