Impl FromStr for Trait Object

I know the orphan rule but why is this working with Box then? If change the pointer to be a Box this compiles just fine? The orphan rule doesn't apply to Box?

P.S. (This is a minimal example, in my real program I need it to be an Arc. Or it could be a Box but I would need to have a way to convert it to Arc which doesn't seem possible? )

use std::str::FromStr;
use std::sync::Arc;


trait Pet {
}

struct Cat();
struct Dog();

impl Pet for Cat{}
impl Pet for Dog{}

impl FromStr for Arc<dyn Pet> {
    type Err = ();
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "cat" => Ok(Arc::new(Cat{})),
            "dog" => Ok(Arc::new(Dog{})),
            _ => Err(())
        }
    }
}

fn main() {
    let pet = "cat".parse::<Arc<dyn Pet>>();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> src/main.rs:14:1
   |
14 | impl FromStr for Arc<dyn Pet> {
   | ^^^^^^^^^^^^^^^^^------------
   | |                |
   | |                `std::sync::Arc` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

error: aborting due to previous error

For more information about this error, try `rustc --explain E0117`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Not sure about the Box vs. Arc thing, but Arc<T> implements From<Box<T>>, so you can do

let pet: Arc<dyn Pet> = "cat".parse::<Box<_>>().unwrap().into();

Since the Box<T> has #[fundamental] attribute, the Box<LocalType> can be considered as local type within the orphan rule.

https://rust-lang.github.io/rfcs/2451-re-rebalancing-coherence.html

3 Likes

This isn't directly related to your question around coherence, but it feels a little weird to be attaching new trait implementations (FromStr in this case) to a trait object. I feel most people would implement this as a free function (e.g. fn parse_pet(s: &str) -> Result<Arc<dyn Pet>, Error>) instead.

Free functions tend to be more discoverable than trait implementations because they appear in the API docs for a module instead of being buried at the bottom of the Pet trait's page. From what I've seen trait objects tend to be used primarily to allow dynamic dispatch on the trait they represent, and other than anomalies like Any::downcast() you don't often see them having their own methods or trait impls.

Of course, this is just convention and personal preference. It may make perfect sense to implement FromStr in your use case.

3 Likes

Unfortunately Box to Arc conversion has to reallocate, so it's less efficient than T to Arc<T> conversion.

1 Like

Thank you for you input guys, looks like I have to drop FromStr and use a free standing function as @Michael-F-Bryan suggested.
Also lesson learned - you can not convert Box to Arc for free without reallocating.
(More info here https://www.reddit.com/r/rust/comments/bqhfoh/turning_a_boxt_into_an_rcarct_without_allocating/)

Similarly,

impl Debug for Arc<dyn Trait>

is not possible, which is very unfortunate. How would one workaround this?

If it’s not enough just to have trait Trait : Debug and have Arc use the wrapped trait’s implementation, then I think I’d write a wrapper type with one field that would have its own implementations of things like FromStr and Debug, possibly with a Deref that delegated to Arc’s Deref.

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.