On this page

Strings and Path

&str is a reference to a byte slice guaranteed to be UTF-8 (String is its heap-allocated version). It cannot be indexed via s[0] (because UTF-8 characters are 1-4 bytes, O(1) indexing is not feasible). OsStr is the platform-native string (bytes on Unix, WTF-8 on Windows), and Path is a path wrapper around OsStr—the correct approach for cross-platform file operations is to always use Path/PathBuf rather than string concatenation.

String vs &str: owned vs borrowed

let s: String = String::from("hello");  // Heap-allocated, mutable, owned
let r: &str = "hello";                  // Static/borrowed, immutable

String = Vec<u8> + UTF-8 validity guarantee—the compiler ensures that the content of String is always valid UTF-8. You can retrieve the underlying Vec<u8> via into_bytes() (discarding the UTF-8 guarantee), or construct from bytes via from_utf8() (which returns a Result; invalid UTF-8 yields an Err).

Why you can't use s[0]

In C, s[0] returns a byte—fine for ASCII, but completely incorrect for multilingual text. In Python, s[0] returns the first Unicode code point—but code points are not always "visible characters" (e.g., é might be represented as e + ́, two code points).

Rust does not expose this access with hidden costs via indexing syntax:

let s = "こんにちは";                  // 5 characters, 15 bytes in UTF-8
// let c = s[0];                      // COMPILE ERROR: no indexing for String
let c = s.chars().nth(2).unwrap();    // 'に' — O(2): must skip the first 2 characters

UTF-8 is a variable-length encoding: ASCII characters are 1 byte, most Western/Middle Eastern characters are 2 bytes, CJK characters are 3 bytes, and emojis are 4 bytes. s[n] cannot be O(1) in Rust—so it is simply not provided. Programmers must explicitly write .chars().nth(n), acknowledging "this requires scanning."

OsStr/OsString: Non-UTF-8 System Strings

On Unix, filenames are just [u8]—they can be arbitrary bytes and are not guaranteed to be UTF-8. On Windows, filenames are WTF-8 (close to UTF-16). OsStr is a cross-platform abstraction—it does not guarantee UTF-8 content but allows safe passing and concatenation:

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

let p = Path::new("/usr/bin");
let parent = p.parent().unwrap();    // /usr
let file = p.file_name().unwrap();   // bin
let full: PathBuf = p.join("subdir").join("file.txt");  // /usr/bin/subdir/file.txt

Path is merely a wrapper around OsStr, adding path-specific methods (parent, extension, components, join). PathBuf is the owned version. The key point is: Path does not require content to be converted to String—you can operate directly on Path without first performing UTF-8 validation.

References

  • Rust Book: Chapter 8.2
  • Rust Reference: OsStr, Path

Keywords: String, &str, UTF-8, char, OsStr, Path, PathBuf, indexing, Unicode