Skip to content

Commit 3a3227c

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 3a3227c

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)