Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Builtin I/O Operations

strict-path provides safe I/O helpers that maintain boundary security while eliminating the need for raw std::fs calls on leaked paths.

Why Use Builtin Methods?

Security: All operations stay within validated boundaries
Ergonomics: No need to call .interop_path() for common operations
Correctness: Type-checked paths guarantee safety at compile time

File Operations

Reading Files

#![allow(unused)]
fn main() {
use strict_path::PathBoundary;

let config_dir = PathBoundary::try_new("/etc/myapp")?;
let config_file = config_dir.strict_join("config.toml")?;

// Read entire file as string
let contents = config_file.read_to_string()?;

// Read as bytes
let bytes = config_file.read()?;
}

Writing Files

#![allow(unused)]
fn main() {
use strict_path::VirtualRoot;

let user_docs = VirtualRoot::try_new("/home/user/documents")?;
let report = user_docs.virtual_join("reports/summary.txt")?;

// Ensure parent directories exist
report.create_parent_dir_all()?;

// Write string content
report.write("Monthly Summary\n\nTotal: 42")?;

// Write bytes
report.write_bytes(b"Binary data")?;
}

File Handles

For streaming or more control, use file handles:

#![allow(unused)]
fn main() {
use std::io::{Read, Write};

// Create or truncate file, get writable handle
let mut file = report.create_file()?;
file.write_all(b"Line 1\n")?;
file.write_all(b"Line 2\n")?;

// Open for reading
let mut file = report.open_file()?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
}

Directory Operations

Creating Directories

#![allow(unused)]
fn main() {
// Create single directory (parent must exist)
let subdir = config_dir.strict_join("plugins")?;
subdir.create_dir()?;

// Create all parent directories if missing
let nested = config_dir.strict_join("data/cache/temp")?;
nested.create_dir_all()?;

// Non-recursive variant (VirtualPath)
let vdir = user_docs.virtual_join("archive")?;
vdir.create_dir_non_recursive()?; // Fails if parent missing
}

Listing Directory Contents

#![allow(unused)]
fn main() {
// Discovery: enumerate entries
for entry in config_dir.read_dir()? {
    let entry = entry?;
    let name = entry.file_name();
    
    // Re-validate discovered path before use
    let validated = config_dir.strict_join(&name)?;
    println!("Found: {}", validated.strictpath_display());
}
}

Removing Directories

#![allow(unused)]
fn main() {
// Remove empty directory
subdir.remove_dir()?;

// Remove directory and all contents (dangerous!)
nested.remove_dir_all()?;
}

Metadata Operations

Checking Existence and Type

#![allow(unused)]
fn main() {
// Check if path exists
if config_file.exists() {
    println!("Config file found");
}

// Check type
if config_file.is_file() {
    println!("It's a file");
}

if config_dir.is_dir() {
    println!("It's a directory");
}
}

Getting Metadata

#![allow(unused)]
fn main() {
// Get full metadata
let metadata = config_file.metadata()?;

println!("Size: {} bytes", metadata.len());
println!("Read-only: {}", metadata.permissions().readonly());
println!("Modified: {:?}", metadata.modified()?);
}
#![allow(unused)]
fn main() {
// Get metadata without following symlinks
let link_meta = config_file.symlink_metadata()?;

if link_meta.is_symlink() {
    println!("This is a symbolic link");
}
}

Copying Files

#![allow(unused)]
fn main() {
let source = config_dir.strict_join("template.conf")?;
let dest = config_dir.strict_join("active.conf")?;

// Copy file, returns bytes copied
let bytes = source.strict_copy(&dest)?;
println!("Copied {} bytes", bytes);
}

Renaming/Moving

#![allow(unused)]
fn main() {
let old_name = config_dir.strict_join("draft.txt")?;
let new_name = config_dir.strict_join("final.txt")?;

// Move/rename within same boundary
old_name.strict_rename(&new_name)?;
}
#![allow(unused)]
fn main() {
// Symbolic link
let target = config_dir.strict_join("current")?;
let link = config_dir.strict_join("link-to-current")?;
target.strict_symlink(&link)?;

// Hard link
target.strict_hard_link(&link)?;
}

Builtin vs std::fs Comparison

Operationstrict-pathstd::fsWhy Prefer Builtin?
Read file.read_to_string()?fs::read_to_string(path.interop_path())?Shorter, stays typed
Write file.write("content")?fs::write(path.interop_path(), "content")?No interop needed
Copy file.strict_copy(&dest)?fs::copy(src.interop_path(), dst.interop_path())?Both paths validated
Metadata.metadata()?fs::metadata(path.interop_path())?Cleaner, same result
Create dir.create_dir_all()?fs::create_dir_all(path.interop_path())?Type-safe path

When to Use interop_path()

Only for unavoidable third-party crates that demand AsRef<Path>:

#![allow(unused)]
fn main() {
// ✅ GOOD: Third-party crate requires AsRef<Path>
let image = config_dir.strict_join("logo.png")?;
let img = image::open(image.interop_path())?; // No choice

// ❌ BAD: Using interop when builtin exists
std::fs::read_to_string(image.interop_path())?; // Use .read_to_string() instead

// ✅ GOOD: Use builtin
let contents = image.read_to_string()?;
}

VirtualPath I/O Operations

All methods work on VirtualPath too, maintaining virtual display semantics:

#![allow(unused)]
fn main() {
let user_root = VirtualRoot::try_new("/var/lib/app/users/alice")?;
let doc = user_root.virtual_join("documents/readme.txt")?;

// All I/O operations available
doc.create_parent_dir_all()?;
doc.write("Welcome to your virtual filesystem!")?;

// Display shows virtual path
println!("Wrote to: {}", doc.virtualpath_display()); // "/documents/readme.txt"

// But I/O happens at real system location
println!("System location: {}", doc.as_unvirtual().strictpath_display());
}

Performance Notes

No overhead: Builtin methods call std::fs internally with zero abstraction cost.
Same syscalls: Operations compile to identical machine code as direct std::fs usage.
Validation cost: Only paid once during strict_join/virtual_join, not on every I/O operation.

Summary

  • Use builtin methods for all common I/O operations
  • Reserve .interop_path() for third-party crates
  • Both StrictPath and VirtualPath support the full I/O API
  • No performance penalty vs raw std::fs
  • Type safety and boundary security maintained automatically