written on Sunday, March 23, 2025
Sometimes in Rust, you need to convert a string into a value of a specific type (for example, converting a string to an integer).
For this, the standard library provides the rather useful FromStr trait. In short, FromStr can convert from a &str into a value of any compatible type. If the conversion fails, an error value is returned. It's unfortunately not guaranteed that this value is an actual Error type, but overall, the trait is pretty useful.
It has however a drawback: it takes a &str and not a String which makes it wasteful in situations where your input is a String. This means that you will end up with a useless clone if do not actually need the conversion. Why would you do that? Well consider this type of API:
let arg1: i64 = parser.next_value()?;
let arg2: String = parser.next_value()?;
In such cases, having a conversion that works directly with String values would be helpful. To solve this, we can introduce a new trait: FromString, which does the following:
We start by defining a type alias for our error:
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
You can be more creative here if you want. The benefit of using this directly is that a lot of types can be converted into that error, even if they are not errors themselves. For instance a FromStr that returns a bare String as error can leverage the standard library's blanket conversion implementation to Error.
Then we define the FromString trait:
pub trait FromString: Sized {
fn from_string(s: String) -> Result<Self, Error>;
}
To implement it, we provide a blanket implementation for all types that implement FromStr, where the error can be converted into our boxed error. As mentioned before, this even works for FromStr where Err: String. We also add a special case for when the input and output types are both String, using transmute_copy to avoid a clone:
use std::any::TypeId;
use std::mem::{ManuallyDrop, transmute_copy};
use std::str::FromStr;
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)
}
}
}
Why transmute_copy? We use it instead of the regular transmute? because Rust requires both types to have a known size at compile time for transmute to work. Due to limitations a generic T has an unknown size which would cause a hypothetical transmute call to fail with a compile time error. There is nightly-only transmute_unchecked which does not have that issue, but sadly we cannot use it. Another, even nicer solution, would be to have specialization, but sadly that is not stable either. It would avoid the use of unsafe though.
We can also add a helper function to make calling this trait easier:
pub fn from_string<T, S>(s: S) -> Result<T, Error>
where
T: FromString,
S: Into<String>,
{
FromString::from_string(s.into())
}
The Into might be a bit ridiculous here (isn't the whole point not to clone?), but it makes it easy to test this with static string literals.
Finally here is an example of how to use this:
let s: String = from_string("Hello World").unwrap();
let i: i64 = from_string("42").unwrap();
Hopefully, this utility is useful in your own codebase when wanting to abstract over string conversions.
If you need it exactly as implemented, I also published it as a simple crate.
Postscriptum:
A big thank-you goes to David Tolnay and a few others who pointed out that this can be done with transmute_copy.
Another note: TypeId::of call requires V to be 'static. This is okay for this use, but there are some hypothetical cases where this is not helpful. In that case there is the excellent typeid crate which provides a ConstTypeId, which is like TypeId but is constructible in const in stable Rust.