diff --git a/library/std/src/ffi/os_str.rs b/library/std/src/ffi/os_str.rs
index f2bbcc85cecda..29f4d41822589 100644
--- a/library/std/src/ffi/os_str.rs
+++ b/library/std/src/ffi/os_str.rs
@@ -692,7 +692,7 @@ impl OsStr {
                   without modifying the original"]
     #[inline]
     pub fn to_str(&self) -> Option<&str> {
-        self.inner.to_str()
+        self.inner.to_str().ok()
     }
 
     /// Converts an `OsStr` to a [Cow]<[str]>.
@@ -1101,6 +1101,24 @@ impl<'a> From> for OsString {
     }
 }
 
+#[stable(feature = "str_tryfrom_osstr_impl", since = "CURRENT_RUSTC_VERSION")]
+impl<'a> TryFrom<&'a OsStr> for &'a str {
+    type Error = crate::str::Utf8Error;
+
+    /// Tries to convert an `&OsStr` to a `&str`.
+    ///
+    /// ```
+    /// use std::ffi::OsStr;
+    ///
+    /// let os_str = OsStr::new("foo");
+    /// let as_str = <&str>::try_from(os_str).unwrap();
+    /// assert_eq!(as_str, "foo");
+    /// ```
+    fn try_from(value: &'a OsStr) -> Result {
+        value.inner.to_str()
+    }
+}
+
 #[stable(feature = "box_default_extra", since = "1.17.0")]
 impl Default for Box {
     #[inline]
diff --git a/library/std/src/sys/unix/os_str.rs b/library/std/src/sys/unix/os_str.rs
index ccbc182240cf3..b88107479ceed 100644
--- a/library/std/src/sys/unix/os_str.rs
+++ b/library/std/src/sys/unix/os_str.rs
@@ -195,8 +195,8 @@ impl Slice {
         Slice::from_u8_slice(s.as_bytes())
     }
 
-    pub fn to_str(&self) -> Option<&str> {
-        str::from_utf8(&self.inner).ok()
+    pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> {
+        str::from_utf8(&self.inner)
     }
 
     pub fn to_string_lossy(&self) -> Cow<'_, str> {
diff --git a/library/std/src/sys/windows/os_str.rs b/library/std/src/sys/windows/os_str.rs
index 11883f15022f6..aca2b739226d4 100644
--- a/library/std/src/sys/windows/os_str.rs
+++ b/library/std/src/sys/windows/os_str.rs
@@ -155,7 +155,7 @@ impl Slice {
         unsafe { mem::transmute(Wtf8::from_str(s)) }
     }
 
-    pub fn to_str(&self) -> Option<&str> {
+    pub fn to_str(&self) -> Result<&str, crate::str::Utf8Error> {
         self.inner.as_str()
     }
 
diff --git a/library/std/src/sys_common/wtf8.rs b/library/std/src/sys_common/wtf8.rs
index 57fa4989358a4..f357824aee138 100644
--- a/library/std/src/sys_common/wtf8.rs
+++ b/library/std/src/sys_common/wtf8.rs
@@ -566,13 +566,8 @@ impl Wtf8 {
     ///
     /// This does not copy the data.
     #[inline]
-    pub fn as_str(&self) -> Option<&str> {
-        // Well-formed WTF-8 is also well-formed UTF-8
-        // if and only if it contains no surrogate.
-        match self.next_surrogate(0) {
-            None => Some(unsafe { str::from_utf8_unchecked(&self.bytes) }),
-            Some(_) => None,
-        }
+    pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
+        str::from_utf8(&self.bytes)
     }
 
     /// Lossily converts the string to UTF-8.
diff --git a/library/std/src/sys_common/wtf8/tests.rs b/library/std/src/sys_common/wtf8/tests.rs
index 931996791fbe5..cb6391458b040 100644
--- a/library/std/src/sys_common/wtf8/tests.rs
+++ b/library/std/src/sys_common/wtf8/tests.rs
@@ -354,11 +354,11 @@ fn wtf8_code_points() {
 
 #[test]
 fn wtf8_as_str() {
-    assert_eq!(Wtf8::from_str("").as_str(), Some(""));
-    assert_eq!(Wtf8::from_str("aé 💩").as_str(), Some("aé 💩"));
+    assert_eq!(Wtf8::from_str("").as_str(), Ok(""));
+    assert_eq!(Wtf8::from_str("aé 💩").as_str(), Ok("aé 💩"));
     let mut string = Wtf8Buf::new();
     string.push(CodePoint::from_u32(0xD800).unwrap());
-    assert_eq!(string.as_str(), None);
+    assert!(string.as_str().is_err());
 }
 
 #[test]