How to copy struct type (not value) being different type, but with same fields

Viewed 239

That's not regarding Copy trait. I thought about it while designing API, where different requests have same fields. I don't want to copypaste declarations (since that would require copy-pasting & later synchronising e.g. validations), but don't want to use same types for different interfaces to prevent occasional confusion. But it's really generic problem. Here's the illustration.

We have two structs:

struct DataCreateRequest {
    name: String
    // ... 100 more fields
}

struct DataUpdateRequest {
    name: String
    // ... 100 more fields
}

What I would like to do is similar to type aliasing, but to make types distinct, something like this:

struct DataCreateRequest {
    name: String
    // ... 100 more fields
}


clone_type DataUpdateRequest = DataCreateRequest; // could be some macro?
let upd: DataUpdateRequest = DataCreateRequest { ... }; // compiler error, since those are different types

So that we reduce duplication, and still re-specialise them if different request appears later.

Any ideas? Any trait, wrapper, macro magic comes to mind?:)

1 Answers

There are a few ways that might prove useful, but in general a struct cannot "inherit" from another struct in an OOP way.

One way is with wrapper types:

struct LargeStruct {
  // lots of fields
}

struct Request(LargeStruct);
struct Response(LargeStruct);

fn foo(request: Request) -> Response {
  Response(request.0)
}

This prevents accidentally passing a Request where a Response is expected, and probably the easiest way.

A macro could also work:

macro_rules! foo {
  ($name:ident) => {
    struct $name {
      // fields
    }
  }
}

// then create your structs:
foo!(Request);
foo!(Response);

Personally this seems like a bit of a hack, and you essentially "hide" from the compiler the fact that Request and Response have the same fields, so if you wanted to trivially convert a Request to a Response you'd need something like this:

fn foo(request: Request) -> Response {
  Response {
    field1: request.field1,
    // etc as long as you can tolerate
  }
}
Related