from_string/
lib.rs

1//! A crate that provides a [`FromString`] trait that works similar to
2//! [`FromStr`] but operates on owned strings.  It's implemented for all types
3//! that implement [`FromStr`] with compatible errors and it has a fast-path
4//! for the no-op string to string conversion.
5//!
6//! For convenience reasons it is constrained to conversions that return a
7//! [`Box<dyn Error + Send + Sync>`](std::error::Error).
8//!
9//! ```
10//! use from_string::from_string;
11//!
12//! let s: String = from_string("Hello World!").unwrap();
13//! let i: u64 = from_string("42").unwrap();
14//! ```
15use std::any::TypeId;
16use std::mem::{ManuallyDrop, transmute_copy};
17use std::str::FromStr;
18
19/// The error type for all conversions.
20pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
21
22/// Parse a value from an owned string
23///
24/// [`FromString`] is implemented for all compatible [`FromStr`] types.  Those are
25/// all types with an implementation that fails with an error that can be converted
26/// into a `Box<dyn Error + Send + Sync>`.
27///
28/// This trait cannot be be implemented for custom types because of the blanket
29/// implementation.  A more ergonomic way to call this is the [`from_string`]
30/// function.
31pub trait FromString: Sized {
32    /// Parses a string `s`` to return a value of this type.
33    fn from_string(s: String) -> Result<Self, Error>;
34}
35
36impl<T> FromString for T
37where
38    T: FromStr<Err: Into<Error>> + 'static,
39{
40    fn from_string(s: String) -> Result<Self, Error> {
41        if TypeId::of::<T>() == TypeId::of::<String>() {
42            Ok(unsafe { transmute_copy(&ManuallyDrop::new(s)) })
43        } else {
44            T::from_str(&s).map_err(Into::into)
45        }
46    }
47}
48
49/// Utility function to call [`FromString::from_string`].
50///
51/// ```
52/// # use from_string::from_string;
53/// let s: String = from_string("Hello World").unwrap();
54/// let i: i64 = from_string("42").unwrap();
55/// ```
56pub fn from_string<T, S>(s: S) -> Result<T, Error>
57where
58    T: FromString,
59    S: Into<String>,
60{
61    FromString::from_string(s.into())
62}
63
64#[test]
65fn test_conversion() {
66    let s: String = from_string("Hello World").unwrap();
67    let i: i64 = from_string("42").unwrap();
68    assert_eq!(s, "Hello World");
69    assert_eq!(i, 42);
70}
71
72#[test]
73fn test_failed_conversion() {
74    let err = from_string::<i64, _>("42aaaa").unwrap_err();
75    assert_eq!(err.to_string(), "invalid digit found in string");
76
77    #[derive(Debug)]
78    struct X;
79
80    impl FromStr for X {
81        type Err = String;
82
83        fn from_str(_: &str) -> Result<Self, Self::Err> {
84            Err("i hate this".into())
85        }
86    }
87
88    let err = from_string::<X, _>("42aaaa").unwrap_err();
89    assert_eq!(err.to_string(), "i hate this");
90}