use std::any::TypeId;
use std::mem::{ManuallyDrop, transmute_copy};
use std::str::FromStr;
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
pub trait FromString: Sized {
fn from_string(s: String) -> Result<Self, Error>;
}
impl<T> FromString for T
where
T: FromStr<Err: Into<Error>> + 'static,
{
fn from_string(s: String) -> Result<Self, Error> {
if TypeId::of::<T>() == TypeId::of::<String>() {
Ok(unsafe { transmute_copy(&ManuallyDrop::new(s)) })
} else {
T::from_str(&s).map_err(Into::into)
}
}
}
pub fn from_string<T, S>(s: S) -> Result<T, Error>
where
T: FromString,
S: Into<String>,
{
FromString::from_string(s.into())
}
#[test]
fn test_conversion() {
let s: String = from_string("Hello World").unwrap();
let i: i64 = from_string("42").unwrap();
assert_eq!(s, "Hello World");
assert_eq!(i, 42);
}
#[test]
fn test_failed_conversion() {
let err = from_string::<i64, _>("42aaaa").unwrap_err();
assert_eq!(err.to_string(), "invalid digit found in string");
#[derive(Debug)]
struct X;
impl FromStr for X {
type Err = String;
fn from_str(_: &str) -> Result<Self, Self::Err> {
Err("i hate this".into())
}
}
let err = from_string::<X, _>("42aaaa").unwrap_err();
assert_eq!(err.to_string(), "i hate this");
}