---
title: Strings and Path
url: https://doc.liz6.com/en/rust/10-standard-library-depth/03-strings-and-path
locale: en
area: rust
tags:
- rust
- standard-library-depth
date: 2026-06-30
modified: 2026-07-16
description: '&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.'
---

# 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

```rust
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:

```rust
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:

```rust
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*
