Need help designing a data structure

I'm trying to design a system but I'm having trouble making it fit into Rust's type system.

My library involves multiple backends which support multiple widgets. To do this, I want to have a Backend trait, and a Widget<B: Backend> trait. So far so good. But then, suppose I have a particular widget called Textbox. I would like its implementation for BackendA to have a different data structure than its implementation for BackendB. Here's where I run into problems:

  • If I have an enum of different backend impls of Textbox, then I wouldn't be able to create a new Backend in another crate and have it still work.
  • If I have the widget trait just point to the backend-specific type (e.g TextboxForBackendA), then I'd have to enumerate all the possible widgets I could have in the widget trait.

I kinda want something like:

struct Textbox;

struct Textbox<BackendA> {
    field1: ty1,
    field2: ty2,
}
struct Textbox<BackendB> {
    field3: ty3,
    field4: ty4,
}

Is the problem I'm trying to solve possible to implement using Rust's type system?

How about this?

struct BackendA {
    field1: ty1,
    field2: ty2,
}

struct BackendB {
    field3: ty3,
    field4: ty4,
}

trait Backend {}
impl Backend for BackendA {}
impl Backend for BackendB {}

struct Textbox<B: Backend> {
    backend: B
}

Or you could add an extra level of indirection if it makes more sense:

struct BackendA;
struct BackendB;

trait Backend {
    type Textbox;
}

impl Backend for BackendA {
    type Textbox = TextboxBackendA;
}

impl Backend for BackendB {
    type Textbox = TextboxBackendB;
}

struct TextboxBackendA {
    field1: ty1,
    field2: ty2,
}

struct TextboxBackendB {
    field3: ty3,
    field4: ty4,
}

struct Textbox<B: Backend> {
    backend: B::Textbox
}

Your second code sample is closer to what I want, but that creates the problem of the Backend now having to enumerate all widget types again. I need Backends and Widgets to be able to be created arbitrarily and still fit into the system.

You can invert the associated types:

struct BackendA;
struct BackendB;

trait Backend {}

impl Backend for BackendA {}
impl Backend for BackendB {}

trait TextboxBackend {
    type Backend: Backend;
}

struct TextboxBackendA {
    field1: String,
    field2: String,
}
impl TextboxBackend for TextboxBackendA {
    type Backend = BackendA;
}

struct TextboxBackendB {
    field3: u32,
    field4: u32,
}
impl TextboxBackend for TextboxBackendB {
    type Backend = BackendB;
}

struct Textbox<B: TextboxBackend> {
    backend: B,
}

fn takes_textbox_with_backend<TB, B>(textbox: Textbox<TB>, backend: B)
where
    TB: TextboxBackend<Backend = B>,
    B: Backend,
{
    // do stuff
}

Notice how you can tie the types together with where clauses in the takes_textbox_with_backend function.

Thanks, this really helps! Although this complicates things it lets me represent the concepts I want. I'll probably eventually use some macros to clean it up.