Skip to content

Commit e5b0618

Browse files
committed
Auto merge of #151822 - Kixunil:tryfrom-osstring-for-string, r=<try>
Implement `TryFrom<OsString>` for `String`
2 parents 36e2b8a + 3a3227c commit e5b0618

3 files changed

Lines changed: 55 additions & 9 deletions

File tree

library/alloc/src/string.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -386,11 +386,16 @@ pub struct String {
386386
/// assert_eq!(vec![0, 159], value.unwrap_err().into_bytes());
387387
/// ```
388388
#[stable(feature = "rust1", since = "1.0.0")]
389+
#[rustc_has_incoherent_inherent_impls]
389390
#[cfg_attr(not(no_global_oom_handling), derive(Clone))]
390391
#[derive(Debug, PartialEq, Eq)]
391-
pub struct FromUtf8Error {
392-
bytes: Vec<u8>,
393-
error: Utf8Error,
392+
pub struct FromUtf8Error<Input = Vec<u8>> {
393+
#[doc(hidden)]
394+
#[unstable(feature = "from_utf8_error_internals", issue = "none")]
395+
pub input: Input,
396+
#[doc(hidden)]
397+
#[unstable(feature = "from_utf8_error_internals", issue = "none")]
398+
pub error: Utf8Error,
394399
}
395400

396401
/// A possible error value when converting a `String` from a UTF-16 byte slice.
@@ -560,7 +565,7 @@ impl String {
560565
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error> {
561566
match str::from_utf8(&vec) {
562567
Ok(..) => Ok(String { vec }),
563-
Err(e) => Err(FromUtf8Error { bytes: vec, error: e }),
568+
Err(e) => Err(FromUtf8Error { input: vec, error: e }),
564569
}
565570
}
566571

@@ -2224,7 +2229,7 @@ impl FromUtf8Error {
22242229
#[must_use]
22252230
#[stable(feature = "from_utf8_error_as_bytes", since = "1.26.0")]
22262231
pub fn as_bytes(&self) -> &[u8] {
2227-
&self.bytes[..]
2232+
&self.input[..]
22282233
}
22292234

22302235
/// Converts the bytes into a `String` lossily, substituting invalid UTF-8
@@ -2251,19 +2256,19 @@ impl FromUtf8Error {
22512256
const REPLACEMENT: &str = "\u{FFFD}";
22522257

22532258
let mut res = {
2254-
let mut v = Vec::with_capacity(self.bytes.len());
2259+
let mut v = Vec::with_capacity(self.input.len());
22552260

22562261
// `Utf8Error::valid_up_to` returns the maximum index of validated
22572262
// UTF-8 bytes. Copy the valid bytes into the output buffer.
2258-
v.extend_from_slice(&self.bytes[..self.error.valid_up_to()]);
2263+
v.extend_from_slice(&self.input[..self.error.valid_up_to()]);
22592264

22602265
// SAFETY: This is safe because the only bytes present in the buffer
22612266
// were validated as UTF-8 by the call to `String::from_utf8` which
22622267
// produced this `FromUtf8Error`.
22632268
unsafe { String::from_utf8_unchecked(v) }
22642269
};
22652270

2266-
let iter = self.bytes[self.error.valid_up_to()..].utf8_chunks();
2271+
let iter = self.input[self.error.valid_up_to()..].utf8_chunks();
22672272

22682273
for chunk in iter {
22692274
res.push_str(chunk.valid());
@@ -2294,9 +2299,11 @@ impl FromUtf8Error {
22942299
#[must_use = "`self` will be dropped if the result is not used"]
22952300
#[stable(feature = "rust1", since = "1.0.0")]
22962301
pub fn into_bytes(self) -> Vec<u8> {
2297-
self.bytes
2302+
self.input
22982303
}
2304+
}
22992305

2306+
impl<T> FromUtf8Error<T> {
23002307
/// Fetch a `Utf8Error` to get more details about the conversion failure.
23012308
///
23022309
/// The [`Utf8Error`] type provided by [`std::str`] represents an error that may

library/std/src/ffi/os_str.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::hash::{Hash, Hasher};
1111
use crate::ops::{self, Range};
1212
use crate::rc::Rc;
1313
use crate::str::FromStr;
14+
use crate::string::FromUtf8Error;
1415
use crate::sync::Arc;
1516
use crate::sys::os_str::{Buf, Slice};
1617
use crate::sys::{AsInner, FromInner, IntoInner};
@@ -616,6 +617,43 @@ impl From<String> for OsString {
616617
}
617618
}
618619

620+
#[stable(feature = "tryfrom_os_string_for_string", since = "CURRENT_RUSTC_VERSION")]
621+
impl TryFrom<OsString> for String {
622+
type Error = FromUtf8Error<OsString>;
623+
624+
/// Attempts to convert an [`OsString`] into a [`String`].
625+
///
626+
/// This conversion does not allocate or copy memory.
627+
fn try_from(s: OsString) -> Result<Self, Self::Error> {
628+
unsafe {
629+
match s.as_os_str().inner.to_str() {
630+
Ok(_) => Ok(String::from_utf8_unchecked(s.into_encoded_bytes())),
631+
Err(error) => Err(FromUtf8Error { input: s, error }),
632+
}
633+
}
634+
}
635+
}
636+
637+
impl FromUtf8Error<OsString> {
638+
/// Returns an [`OsStr`] slice that was attempted to convert to a `String`.
639+
#[stable(feature = "tryfrom_os_string_for_string", since = "CURRENT_RUSTC_VERSION")]
640+
#[rustc_allow_incoherent_impl]
641+
pub fn as_os_str(&self) -> &OsStr {
642+
&self.input[..]
643+
}
644+
645+
/// Returns the [`OsString`] that was attempted to convert to a `String`.
646+
///
647+
/// This method is carefully constructed to avoid allocation. It will
648+
/// consume the error, moving out the string, so that a copy of the string
649+
/// does not need to be made.
650+
#[stable(feature = "tryfrom_os_string_for_string", since = "CURRENT_RUSTC_VERSION")]
651+
#[rustc_allow_incoherent_impl]
652+
pub fn into_os_string(self) -> OsString {
653+
self.input
654+
}
655+
}
656+
619657
#[stable(feature = "rust1", since = "1.0.0")]
620658
impl<T: ?Sized + AsRef<OsStr>> From<&T> for OsString {
621659
/// Copies any value implementing <code>[AsRef]&lt;[OsStr]&gt;</code>

library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@
287287
#![feature(f128)]
288288
#![feature(ffi_const)]
289289
#![feature(formatting_options)]
290+
#![feature(from_utf8_error_internals)]
290291
#![feature(funnel_shifts)]
291292
#![feature(if_let_guard)]
292293
#![feature(intra_doc_pointers)]

0 commit comments

Comments
 (0)