1

Is it possible to encapsulate a trait object inside a struct?

As far as I can tell, it requires main to know about the type A, no matter how many levels of modules are between. I'm trying to make a default implementation so the internals can remain that. My current use case is with the trait yup_oauth2::GetToken.

My current interface:

// root_project/main.rs
let auth_obj = build_auth_obj("user", "pass");
let root_caller: ApiUser<_> = ApiUser::new(auth_obj);

// api_user/lib.rs
pub struct ApiUser<A> {
    stored_api: API<A>,
}

impl ApiUser<A>
where
    A: yup_oauth2::GetToken,
{
    fn new(auth_obj: A) -> API<A> {
        stored_api: API::new(auth_obj)
    }
}

// api_def/lib.rs
struct Api<A> {
    auth: Refcell<A>,
}

impl<A> Api<A>
where
    A: yup_oauth2::GetToken,
{
    fn new(auth: A) -> Api<A> {
        API {
            auth: RefCell::new(auth),
        }
    }
}

My desired interface:

// root_project/main.rs
let root_caller: ApiUser = ApiUser::new("user", "pass");

// api_user/lib.rs
pub struct ApiUser {
    stored_api: API,
}

impl ApiUser {
    fn new(username: String, password: String) -> API {
        stored_api: API::new(username, password)
    }
}

// api_def/lib.rs
pub struct API(ApiImpl<dyn yup_oauth2::GetToken>);

struct ApiImpl<A> {
    auth: Refcell<A>,
}

impl<A> ApiImpl<A>
where
    A: yup_oauth2::GetToken,
{
    fn new(username: String, password: String) -> ApiImpl<A> {
        ApiImpl {
            auth: RefCell::new(build_auth_obj(username, password)),
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • It looks like your question might be answered by the answers of [How can I avoid a ripple effect from changing a concrete struct to generic?](https://stackoverflow.com/q/44912349/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Feb 18 '20 at 20:20
  • 1
    Thanks for the quick response, it looks like what I want for writing my own modules. When I try it, I get "The trait 'yup_oauth2::GetToken' cannot be made into an object". Is there anything I can do in my code or would a PR for yup_oauth2 be the only way to enable it? – David Fogelson Feb 18 '20 at 20:52
  • That's a limitation of the `GetToken` trait. See questions like [Understanding Traits and Object Safety](https://stackoverflow.com/q/44096235/155423) for further details. – Shepmaster Feb 18 '20 at 20:58
  • I see the issue is https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-02-trait-objects.html#object-safety-is-required-for-trait-objects. Just curious if there is any way I could work around it. I'm good to close the question if "not possible" is the answer in this particular case. – David Fogelson Feb 18 '20 at 21:02
  • 1
    No, it's not currently possible. That being said, most people would just let the compiler infer the type and not try to be explicit: `let root_caller = ApiUser::new(auth_obj);`, just like you did with `auth_obj`. – Shepmaster Feb 18 '20 at 21:06

0 Answers0