1

I have a background in typescript and recently started learning Rust. I am wondering how I would approach something like the following in Rust – or: what's the Rusty way?

// base type, used widely within the application
type User = {
   id: UUID
   name: string
   email: string
}

// -- specialized types --

// might be used only for the API route
// e.g. a POST endpoint where some properties are omitted
type PostUser = Omit<User, "id">
// explanation: the Omit utility type creates a new type `PostUser` based on `user` but removes the property `id`

// or this one where the database holds more properties that are excluded when deserializing
type DBUser = User & { deleted: boolean }
// explanation: this also creates a new type but adds another property `deleted`

This is not possible in Rust and there are no similar techniques, I guess. But how do you guys keep code maintainable?

Do I really have to copy the whole struct? It would be pain in the ass when it comes to refactoring. Or does Rust offer different techniques I am not aware of?

struct User {
    pub id: Uuid,
    pub name: String,
}

// copying might be simple here, but maintaining multiple properties across hundred files?
struct PostUser {
    // pub id: Uuid, // <- The POST endpoint does not receive an id, it's set afterwards
    pub name: String,
}

Thanks for clarifying this for me!

lsc
  • 375
  • 6
  • 15
  • 1
    I'm not sure I fully understand your examples, but maybe this can help you: in Rust, it's easy to "add fields" to a struct: define a new struct which has one field whose type is the former struct, and add other fields. But you can't remove fields. So you have to start with the most "basic" types, and grow them, rather than shrink them. – jthulhu Jun 24 '23 at 17:54
  • 1
    Check out [this related post](https://stackoverflow.com/questions/32552593/is-it-possible-for-one-struct-to-extend-an-existing-struct-keeping-all-the-fiel) – rochard4u Jun 24 '23 at 18:00
  • Thanks for your comment @jthulhu ! I've added additional explanations for the typescript code. I guess you mean composition? One requirement I forgot to mention is to not touch the original structure of the type (e.g. for easier serde operations) – lsc Jun 24 '23 at 18:02
  • 2
    You can configure serde to adapt to the way your type is lain out, so I wouldn't worry about that. The other solution is to duplicate most of type definition, but using macros to avoid code duplication. In general, Rust can be a bit verbose about types (which has the advantage that you can be very precise and take full advantage of the safety provided), which is compensated by a very powerful macro system. – jthulhu Jun 24 '23 at 18:08
  • @rochard4u thanks! That post also claims that there's no equivalent to Rust. However, the suggested methods of composition and using traits seem too complex especially when only small structs are involved. – lsc Jun 24 '23 at 18:09
  • @jthulhu that sounds promising to me! I will dig into serde & macros, thanks for the hint! – lsc Jun 24 '23 at 18:11
  • 1
    Composition with `#[serde(flatten)]` seems like it should work. – cdhowie Jun 24 '23 at 20:45
  • I can understand the motivation behind `DBUser` being `User`+something, but how can we consider `PostUser` (`User`-something) being maintainable? Everywhere a `User` is expected, we cannot use a `PostUser` because one of its properties is missing. `PostUser` should be unrelated to `User` because it's confusing. As told in other comments or related answers, you should probably define a `User` with a minimal set of properties and operations and only add other properties and operations thanks to composition (whatever the language). – prog-fh Jun 24 '23 at 21:42
  • @prog-fh in the typescript example `PostUser` is intended to statically type e.g. a HTTP API. It could further be exposed from a library but wouldn't be used widely within the source code, that's correct. It's meant to offer an interface where especially properties like the id or other computed properties are not included. A function would then take a `PostUser` and create a `User`: `f(PostUser) -> User`. `type PostUser = Omit` is highly maintainable because it will always be an exact copy of the type `User`, except the property `id` is omitted, JSDoc etc. included. – lsc Jun 25 '23 at 07:49

0 Answers0