Impl FromStr for custom trait

Rust newbie here. Let me start with some context before giving a minimal example. I am creating a library crate with some time-related functionality among other things. I have a Time struct which consists of fields for various units:

pub struct Time {
    seconds: Second,
    minutes: Minute,
    hours: Hour,
    days: Day,
    weeks: Week,
}

Note that each of the units has its own type. I realize there are a number of ways I could approach this using enums and traits, and I may very well change my approach later, but I still want to know how to do what I'm trying to do.

I wrote a working FromStr implementation for Time that reads from a config file and generates the corresponding Time instance. An example time string would be "1s2m3h" (1 second, 2 minutes, 3 hours). I'm now trying to refactor things and make the code a bit more modular. I want Time's FromStr to be implemented as a sum over FromStr's on each of the individual unit types. That's fairly straightforward, but there is a lot of repetition, as the logic is essentially unchanged between them, which the only difference between the qualifier character that denotes which unit of time we're working with.

Each of the unit types implements a trait I called Unit. I did this to essentially require common functionality across unit types (get type, get value, get qualifier, add to time, from string, etc.). What I would like is to implement FromStr on the Unit trait, such that Unit::from_str() returns a trait object, and the Time FromStr implementation can just sum all of the units without caring which types they are. After struggling with it for a while, I'm still pretty lost.

Here is a minimal example:

Playground

use core::str::FromStr;


fn main() {
    //
    // A is a trait. I want A::from_str(s) to instantiate a trait object whose
    // underlying type is based on the string argument. In this minimal example,
    // the string is ignored, but in the real problem, I would of course use the
    // string argmuent to inform the output.
    //
    let _a = <&dyn A>::from_str("ignored string").unwrap();
}


//
//  A - This is playing the role of the Unit trait described above.
//
trait A {}

#[derive(Debug)]
struct ParseAError;
//
// Not sure if &dyn A is the right syntax here. I'm still trying to make sense
// of trait objects.
//
impl FromStr for &dyn A {
    type Err = ParseAError;

    fn from_str(_s: &str) -> Result<Self, Self::Err> {
        let thing_b = B::default();
        //
        // Here is where I want to return the underlying B instance as an A
        // trait object. This of course does not compile as written. I think I
        // need to use Box and/or Arc, but nothing I tried works.
        //
        Ok(&thing_b)
    }
}


//
//  B - This is playing the role of the individual types (Second, Minute, etc.)
//
#[derive(Debug, Default)]
struct B;

impl A for B {}

#[derive(Debug)]
struct ParseBError;
//
// This implementation is straightforward, but I want to use the more generic
// Unit one above.
//
impl FromStr for B {
    type Err = ParseBError;

    fn from_str(_s: &str) -> Result<Self, Self::Err> {
        Ok(B::default())
    }
}

Any help is much appreciated. Thanks!

You can't return a reference to something that doesn't yet exist (ie., already owned by someone else). So you can't return a reference to something created inside the function – the owned value would be destroyed once the function returns, so the caller would unconditionally observe a dangling reference. (But the compiler already told you so much.)

You probably want impl FromStr for Box<dyn A> instead, as the box owns its pointee. Playground

1 Like

That pretty much does it! To be clear, I understood what was wrong from the compiler, I just didn't know how to do what was right instead, haha. I didn't realize you could impl FromStr for Box<dyn A>.

It does leave me wondering if there's some way to do this without heap allocations. I saw some stuff about the Sized trait, but I haven't dug too deep into it yet. Maybe that'll be my next question after I wrestle with it some more.

Thanks for the help!

Since FromStr::from_value has to return the result by-value, and dynamically-sized types can't be returned directly by-value, there's no way to do this exactly in this way without some form of heap allocation. (This may be relaxed in the future with the stabilization of the unsized_locals feature. Don't hold your breath, though.)

There are alternative solutions without FromStr, though:

  1. Don't implement FromStr for Box<dyn A> at all. It's pretty weird to see a constructor-like function parsing directly into a trait object like that. If you have the concrete type B, you can always turn it into a Box<dyn A> or a &dyn A (or any other form of trait object, really) if you want it, but you can't go back from dyn A to B. So you should probably prefer parsing into the concrete type and let the caller turn it into a trait object only if needed.
  2. Return impl A instead of dyn A. That still is an existential type, i.e., the concrete type is hidden from the programmer, but it is known to the compiler, so it can be returned directly by-value. However, it does not apply dynamic dispatch, so not sure whether that is satisfactory for you.
  3. Instead of a by-value return, fill a buffer passed in from the outside:
    fn foo(buf: &mut Option<B>) -> &mut dyn A {
        buf.insert(B)
    }
    
    This seems like a pretty pointless complication at this point, though.
1 Like

To elaborate: Box is special in this respect. impl FromStr for Rc<dyn A> won't work, for example, because FromStr is not local to your crate and neither is Rc. But Rust treats Box specially; Box<T> is considered local to your crate as long as T is. In addition to Box, these special rules apply to &, &mut, Pin, and nested combinations thereof. [1]


  1. (There is also an unstable nightly-only feature, fundamental, that lets you do this for your own types). ↩ī¸Ž

2 Likes

Yikes, that doesn't seem super intuitive, at least to me. The good thing is it looks like it's pretty easy to convert a Box to an Rc. I'm sure there are reasons for it being that way that are beyond my current understanding. Thanks for the heads-up!

It's to balance out the orphan rules, as otherwise you couldn't (for example)

  • impl MyTrait for Box<dyn MyTrait>
  • impl<'a> IntoIterator for &'a MyCollection

(Side note, that RFC was written when one coded just Trait instead of dyn Trait.)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.