[ talks ] [ about ] [ publications ] [ posts ] [ home ]
January, 2018
I recently came across a head-scratcher while working on a personal project in Rust. I wasn’t able to find a ready solution so I worked something out on my own. I decided to talk through my solution here for a few reasons. The problem I encountered represents behavior I hadn’t seen before, it’s something I learned from and am interested in some conversation around it. I also think it might help someone who finds themselves in the same boat.
The general problem arose while exploring the FromStr
trait. I wanted to implement it for my own type but ran into a pesky
issue. Implementing the FromStr
trait for my type required
that applications using my library include a
use std::str::FromStr;
to actually use it. This hadn’t been
my experience with implementing traits in the past, the
Display
and Iterator
traits, for example, have
no such corresponding requirement.
Let’s get started with some context. We’ll call my library
from_str_example
and boil things down into the
FromStr
trait as follows.
use std::str::FromStr;
use std::string::ParseError;
pub struct TypeV1 {
: String,
my_string}
impl FromStr for TypeV1 {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self { my_string: String::from(s) })
}
}
From here an application that uses my from_str_example
crate might assume the following will work (I did).
extern crate from_str_example;
use from_str_example::TypeV1;
fn main() {
let var = TypeV1::from_str(
"Today my winged horse is coming").unwrap();
}
But it doesn’t work! Trying to compile results in the following errors:
Compiling myapp v0.1.0 (file:///path/to/my/app)
error[E0599]: no function or associated item named `from_str` found for type `from_str::TypeV1` in the current scope
--> src/main.rs:18:16
|
18 | let test = TypeV1::from_str(
| ^^^^^^^^^^^^^^^^ function or associated item not found in `from_str::TypeV1`
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope, perhaps add a `use` for it:
|
3 | use std::str::FromStr;
|
error: aborting due to previous error
error: Could not compile `myapp`.
Okay. I trust the compiler. Let’s add the use
it
recommends.
extern crate from_str_example;
use from_str_example::TypeV1;
use std::str::FromStr;
fn main() {
let var = TypeV1::from_str(
"Today my winged horse is coming").unwrap();
}
And it works! But wait… I don’t want to require that
use
. I want from_str
to just work for
TypeV1
without requiring anything beyond my library. Okay,
time to rethink. I can just implement my own
from_str
for my type.
So after a revision, that we’ll note by using TypeV2
in
this section, we have the following code in our library.
use std::string::ParseError;
pub struct TypeV2 {
: String,
my_string}
impl TypeV2 {
pub fn from_str(s: &str) -> Result<Self, ParseError> {
Ok(Self { my_string: String::from(s) })
}
}
And voila, the following works just like I’d hoped…
extern crate from_str_example;
use from_str_example::TypeV2;
fn main() {
let var: TypeV2 = TypeV2::from_str(
"and I am carrying you off to the moon").unwrap();
}
Or rather, worked as I’d hoped briefly. My next issue arose
when I wanted to write a generic function that expected the
FromStr
trait. Let’s reduce that function to an example.
Forgive me the where
clause, my intent is to provide as
example, some code with meaningful output. If you decide you’d like to
experiment, feel free to remove the println!
s and revise
the signature to
pub fn generic_print_function<T: FromStr>(_:T)
use std::str::FromStr;
use std::fmt::Debug;
pub fn generic_print_function<T: FromStr + Debug>(var: T)
where <T as std::str::FromStr>::Err: std::fmt::Debug
{
let from_str_result = T::from_str("Wear your boots if you wander today");
println!("{:?}", var);
println!("{:?}", from_str_result);
}
Now a user of the from_str_example
crate is back to
compile errors!
extern crate from_str_example;
use from_str_example::TypeV2;
fn main() {
let var: TypeV2 = TypeV2::from_str(
"and I am carrying you off to the moon").unwrap();
;
generic_print_function(var)}
The above example results in the following compile error:
Compiling from_str v0.1.0 (file:///path/to/my/app)
Compiling myapp v0.1.0 (file:///path/to/my/app)
error[E0277]: the trait bound `from_str::TypeV2: std::str::FromStr` is not satisfied
--> src/main.rs:27:5
|
27 | generic_print_function(var);
| ^^^^^^^^^^^^^^^^^^^^^^ the trait `std::str::FromStr` is not implemented for `from_str::TypeV2`
|
= note: required by `from_str::generic_print_function`
error: aborting due to previous error
error: Could not compile `myapp`.
To learn more, run the command again with --verbose.
Well damn, we just removed the implementation of
FromStr
. Desperate times, they call for desperate
measures.
I began the search engine and stack overflow answers dance. I’m not going to claim that the answer isn’t out there but I wasn’t able to unearth one and after enough time I started feeling devious.
Could I just implement FromStr
and
from_str
unique to my custom type?
As a seasoned C developer I’m accustomed to having to brace for impact when it feels like I’m going out on a limb. But in Rust land, I’ve been empowered by the “hack without fear” mantra. I’m less twitchy and more open to looking for a fight because I’ve got the compiler at my back.
Here’s my approach using TypeV3
to indicate the
shift.
use std::str::FromStr;
use std::string::ParseError;
use std::fmt::Debug;
#[derive(Debug)]
pub struct TypeV3 {
: String,
my_string}
impl TypeV3 {
pub fn from_str(s: &str) -> Result<Self, ParseError> {
Ok(Self { my_string: String::from(s) })
}
}
impl FromStr for TypeV3 {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_str(s)
}
}
pub fn generic_print_function<T: FromStr + Debug>(var: T)
where <T as std::str::FromStr>::Err: std::fmt::Debug
{
let from_str_result = T::from_str("Wear your boots if you wander today");
println!("{:?}", var);
println!("{:?}", from_str_result);
}
It compiled! Time to see if it works…
extern crate from_str_example;
use from_str_example::TypeV3;
fn main() {
let var = TypeV3::from_str(
"and on the moon we will eat rose petals.").unwrap();
;
generic_print_function(var)}
It does work! And in my personal project, this is the approach I took.
Recapping for anyone scanning for the words fix or
success, implement the trait FromStr
as well as a function called from_str
for your
custom type. This allows you to sidestep the requirement that a user of
your library has to add use std::str::FromStr;
to their
project to use yours.
For any questions or if you have any insight around the subject of this post, or just to say hi, reach out at hello@shnewto.dev.
**UPDATE (01.23.2018): As noted by @steveklabnik and
a handful of users on /r/rust/
the initial example presented with TypeV1
will work without
an extra use
statement if a user opts for
str::parse
rather than using TypeV1::from_str
directly.
you can do this by just implementing
— Some(@steveklabnik) (@steveklabnik) January 23, 2018FromStr
! The key is https://t.co/EAgc0lIPXA
you could write this: https://t.co/kc5ehwett2