3

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?:)

Herohtar
  • 5,347
  • 4
  • 31
  • 41
Meredian
  • 930
  • 8
  • 23
  • 2
    why don't use have a common type share by your two structs ? – Stargateur Nov 03 '21 at 10:27
  • Alexey, not exactly. I've checked that one. I know there's no concept of inheritance :) That's more about building cutting down _unnecessary_ duplication without compromising types. For sharing types (as composition) - that would change flat structure. – Meredian Nov 03 '21 at 10:36
  • One thing I got is to create generic & parametrise it with Unit enum, but that requires embedding marker::PhantomData<...> inside, since you can't have unused type in generic. – Meredian Nov 03 '21 at 10:37
  • 5
    `struct Data { name: String, ... } struct DataCreateRequest { d: Data } struct DataUpdateRequest { d: Data }`. Then you either use it as `req.d.name` (not a big deal) or add a couple of helper functions `fn data(&self) -> &Data { &self.d } fn data_mut(&mut self) -> &mut Data { &mut self.d }`. – rodrigo Nov 03 '21 at 12:02
  • 2
    When wrapping just a single field, I tend to use tuple structs: `struct DataCreateRequest(Data);`. The field can be accessed with `request.0`. – BallpointBen Nov 03 '21 at 14:13
  • @BallpointBen: I used to do that too. But too many times I had to add a new field, and then replace all uses of `.0` for `.data` (or add a `.1`, ugh!), so nowadays I write tuple structs more sparsely. – rodrigo Nov 03 '21 at 15:13
  • You could shoot yourself in the foot by doing something like this: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=de2b9a77ed10190b0c5593e9a45ba1f0 – Martin Gallagher Nov 03 '21 at 15:28

1 Answers1

2

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
  }
}
cameron1024
  • 9,083
  • 2
  • 16
  • 36