Skip to content

Commit dec3d5b

Browse files
committed
Implement TryFrom<OsString> for String
Being able to generically convert strings can be beneficial for argument parsing code and similar situations especially in case of conditional conversions. The standard library already provided this converion, just not via a trait. This commit fills the gap by adding the impl. This addition was approved in [ACP 732]. It was requested that `FromUtf8Error` should be made generic over the input and this commit obeys the request. However some challenges were encountered: * The fields were private and the type had no constructor - solved by making the fields public but unstable and `#[doc(hidden)]`. * There is a method to perform lossy conversion and it looks like it should be provided for `OsString` as well - this is not yet resolved. * `into_os_string` method was requested but the types are in different crates - solved with `#[rustc_allow_incoherent_impl]`. [ACP 732]: rust-lang/libs-team#732
1 parent e96bb7e commit dec3d5b

3 files changed

Lines changed: 50 additions & 10 deletions

File tree

library/alloc/src/string.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,13 @@ pub struct String {
388388
#[stable(feature = "rust1", since = "1.0.0")]
389389
#[cfg_attr(not(no_global_oom_handling), derive(Clone))]
390390
#[derive(Debug, PartialEq, Eq)]
391-
pub struct FromUtf8Error {
392-
bytes: Vec<u8>,
393-
error: Utf8Error,
391+
pub struct FromUtf8Error<Input = Vec<u8>> {
392+
#[doc(hidden)]
393+
#[unstable(feature = "from_utf8_error_internals", issue = "none")]
394+
pub input: Input,
395+
#[doc(hidden)]
396+
#[unstable(feature = "from_utf8_error_internals", issue = "none")]
397+
pub error: Utf8Error,
394398
}
395399

396400
/// A possible error value when converting a `String` from a UTF-16 byte slice.
@@ -560,7 +564,7 @@ impl String {
560564
pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error> {
561565
match str::from_utf8(&vec) {
562566
Ok(..) => Ok(String { vec }),
563-
Err(e) => Err(FromUtf8Error { bytes: vec, error: e }),
567+
Err(e) => Err(FromUtf8Error { input: vec, error: e }),
564568
}
565569
}
566570

@@ -2224,7 +2228,7 @@ impl FromUtf8Error {
22242228
#[must_use]
22252229
#[stable(feature = "from_utf8_error_as_bytes", since = "1.26.0")]
22262230
pub fn as_bytes(&self) -> &[u8] {
2227-
&self.bytes[..]
2231+
&self.input[..]
22282232
}
22292233

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

22532257
let mut res = {
2254-
let mut v = Vec::with_capacity(self.bytes.len());
2258+
let mut v = Vec::with_capacity(self.input.len());
22552259

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

22602264
// SAFETY: This is safe because the only bytes present in the buffer
22612265
// were validated as UTF-8 by the call to `String::from_utf8` which
22622266
// produced this `FromUtf8Error`.
22632267
unsafe { String::from_utf8_unchecked(v) }
22642268
};
22652269

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

22682272
for chunk in iter {
22692273
res.push_str(chunk.valid());
@@ -2294,9 +2298,11 @@ impl FromUtf8Error {
22942298
#[must_use = "`self` will be dropped if the result is not used"]
22952299
#[stable(feature = "rust1", since = "1.0.0")]
22962300
pub fn into_bytes(self) -> Vec<u8> {
2297-
self.bytes
2301+
self.input
22982302
}
2303+
}
22992304

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

library/std/src/ffi/os_str.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::collections::TryReserveError;
1010
use crate::hash::{Hash, Hasher};
1111
use crate::ops::{self, Range};
1212
use crate::rc::Rc;
13-
use crate::str::FromStr;
13+
use crate::str::{FromStr, FromUtf8Error};
1414
use crate::sync::Arc;
1515
use crate::sys::os_str::{Buf, Slice};
1616
use crate::sys::{AsInner, FromInner, IntoInner};
@@ -616,6 +616,39 @@ impl From<String> for OsString {
616616
}
617617
}
618618

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