Struct Field & Behavior Composition: Staying DRY in Rust

Good day everyone!

EDIT: Which category should this go in?

I've been learning Rust for about a year now, and feel I have a fairly good understanding of how to at least get things working with minimal complaints with the compiler. Overall I've found the documentation to be fantastic at explaining and documenting the mechanics of the language. However, I'm still trying to learn more about the idiomatic "Rust way" to solve certain design challenges, especially at a macro (bad Rust pun? :laughing:) level. The are many great examples of idiomatic Rust in the Rust Book and the aptly named Rust by Example, but they are focused more on showcasing specific features, rather than on using those features together to structure a larger program.

One of the design challenges I've run into a number of times now is how to structure a program which has many different types that frequently, but not always, share the same fields and/or behavior, so that as much code can be reused at possible. A perfect example of this is:

Developing an API that takes requests, interfaces with a relational database backend, and returns a response.

So I'm proposing a small(ish) design challenge! I know there are various resources around showing how the above example can be solved, but they all, at least from what I've seen, are either tied to a specific ORM/framework implementation, or are more narrowly focused on solving more specific challenges, i.e. using the async-trait crate, struct composition, etc, or simply devolve into esoteric analysis of why or why not things can or cannot work in Rust, what works in stable vs nightly, etc.

Goal

Develop a tutorial/project of sorts showcasing how to use the various struct types, methods, associated functions, generics, traits, lifetimes, pointers, async, associated types, etc., to develop and structure a composable and DRY data model application layer. Using the project result, we want it to be as quick and DRY as possible to add new models to the program.

Why?

Because many programs are primarily concerned with marshaling data back and forth, I think this is a perfect topic to showcase how all of the aforementioned technical features of Rust can work together to solve a common problem the "Rust way". This will be especially useful for those coming from a background in dynamically types and/or object oriented languages (which, according the most recent survey, are also those that have the most trouble picking up and sticking with the language).

I'm willing to work together, to the best of my ability, with the relevant working group/team in order to bring this to a publishable state.

Assumptions

  • There is an adapter being used to actually connect with the backend database. The adapter handles serialization and deserialization to and from the corresponding table model, and also provides an api to query/exec SQL statements.
  • Each table has an associated model in the program.

Requirements

  1. The model will need to expose async methods to find/get, find_all/get_all, update, delete, etc. However, not every model will expose every method, i.e. we don't want every model to be deletable.
  2. Some tables will have a standardized primary key column named id which will also share the same type (we'll use a basic Int for simplicity).
  3. Some tables will use a 'soft-delete' system, where record deletions will be recorded in a bool column, is_deleted, rather than hard-deleting the record from the database. Other tables will just hard-delete.
  4. Some tables will have a foreign key column named owner_id which will be used to confirm that the authenticated user requesting the record is authorized to view it.

Each of these three requirements will have implications for the design of the various method implementations, i.e. on models with an owner, the owner_id needs to be check needs to be added to any SQL statement sent to the backend.

Getting Started

Before getting started I can imagine there needs to be further exploration of the goals, assumptions, and requirements. Perhaps we can further develop them to showcase the use-cases and edge-cases of various features of Rust, while still focused on showcasing the idiomatic was to architect the program.

A naive Typescriptish type composition representation:

interface HasId {
    id: number
}

interface HasIsDeleted {
    is_deleted: bool
}

interface HasOwner {
    owner_id: number
}

interface User extends HasId, HasIsDeleted {
    email: string
    // id: number
    // is_deleted: bool
}

interface Task extends HasId, HasIsDeleted, HasOwner {
    title: string
    is_complete: bool
    // id: number
    // is_deleted: bool
    // owner_id: number
}

// Uses email as PK, not id
// Hard deletes
// No owner
interface Registration {
    email: string
    confirmation_code: string
    code_expiration: string
}

Thank you for reading!

1 Like

probably #announcements ? :grin:

There's not a specific team that exists that would help you on this endeavor, but you can always create a team, albeit not an official Rust team. It'd be interesting to see this exist though.

Hi Havvy, sorry for the late response. I've spoken with someone on the docs team and essentially got the same response. I understand that everyone is really busy with their various assignments. I have no problem helping to put a team together and contributing what I can.

I think it would be great if we could get some people from the core and/or language design teams to contribute. What we need is a document that teaches How to Rust the way Rust is meant to be Rusted (™).

1 Like

to @the-notable
With my point of view.

Rust-lang bit more like Pascal-lang if you have trying pascal-lang they have vcl solution components thats not related to the way of pascal-lang it self works, but that base for all development framework. of course Rust more efficient than pascal. I hope Rust can be like that and once again this was my point of view.

Thanks to Mr. Niklaus Wirth has been create pascal-lang.

I don't have any experience with Pascal and its ecosystem. Maybe someone else does.

2 Likes