Skip to main content

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;