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}