wtf_string/string/os_str.rs
1// Copyright (c) 2026 Mike Grier
2//! Windows `OsStr` / `OsString` interop for the WTF-16 string types.
3//!
4//! On Windows an `OsStr` is stored as WTF-8, so a bridge to WTF-16 cannot borrow
5//! -- it converts once at the boundary via `encode_wide` / `from_wide`. Both
6//! directions are lossless, including for unpaired surrogates, because `OsStr` and
7//! `WtfStr<Wtf16>` are both WTF supersets (D-5/D-8). A borrowing `AsRef<OsStr>` is
8//! deliberately *not* provided: the two types have different backing widths, so no
9//! zero-copy `&OsStr` view of `u16` storage exists (see DESIGN-NOTES D-14).
10
11use std::ffi::{OsStr, OsString};
12use std::os::windows::ffi::{OsStrExt, OsStringExt};
13
14use super::{Wtf16Str, Wtf16String};
15
16impl Wtf16String {
17 /// Encode an [`OsStr`] into owned WTF-16, converting once at the boundary.
18 ///
19 /// Lossless: unpaired surrogates survive, so
20 /// `from_os_str(x).to_os_string() == x`. This is the owning analog of
21 /// collecting [`OsStrExt::encode_wide`].
22 #[must_use]
23 pub fn from_os_str(s: &OsStr) -> Self {
24 Self::from_encoded(s.encode_wide().collect())
25 }
26
27 /// Build from already-wide code units: the [`OsStringExt::from_wide`] analog.
28 ///
29 /// Identical to [`from_units`](Self::from_units), named for drop-in
30 /// familiarity when replacing an `OsString::from_wide` call site.
31 #[must_use]
32 pub fn from_wide(units: &[u16]) -> Self {
33 Self::from_units(units)
34 }
35}
36
37impl Wtf16Str {
38 /// Decode the content into an owned [`OsString`], losslessly.
39 ///
40 /// The [`OsStringExt::from_wide`] bridge: unpaired surrogates are preserved.
41 #[must_use]
42 pub fn to_os_string(&self) -> OsString {
43 OsString::from_wide(self.as_units())
44 }
45
46 /// Iterate the content as wide code units, zero-copy: the
47 /// [`OsStrExt::encode_wide`] analog over our own slice.
48 pub fn encode_wide(&self) -> impl Iterator<Item = u16> + '_ {
49 self.as_units().iter().copied()
50 }
51}
52
53impl From<&OsStr> for Wtf16String {
54 fn from(s: &OsStr) -> Self {
55 Self::from_os_str(s)
56 }
57}
58
59impl From<&OsString> for Wtf16String {
60 fn from(s: &OsString) -> Self {
61 Self::from_os_str(s)
62 }
63}
64
65impl From<OsString> for Wtf16String {
66 fn from(s: OsString) -> Self {
67 Self::from_os_str(&s)
68 }
69}
70
71impl From<&Wtf16Str> for OsString {
72 fn from(s: &Wtf16Str) -> Self {
73 s.to_os_string()
74 }
75}
76
77impl From<&Wtf16String> for OsString {
78 fn from(s: &Wtf16String) -> Self {
79 s.to_os_string()
80 }
81}
82
83impl From<Wtf16String> for OsString {
84 fn from(s: Wtf16String) -> Self {
85 s.to_os_string()
86 }
87}
88
89#[cfg(test)]
90mod tests;