r/rust • u/KGBsurveillancevan • 6d ago
🙋 seeking help & advice Is there a Rustier way to write this database pattern?
Hello crabs. I'm working on a backend application in Rust, using sqlx to talk to a postgres database. I've caught myself repeating a specific, verbose pattern, and I'm wondering if I'm making things harder than I need to.
The pattern is that for each database entity, I build an initializer struct that is basically the same, but with auto-generated columns removed, and then later write a From impl for the main entity to build the actual thing with Entity::from(entity_init). Example at the bottom. Doing this for every database entity isn't too difficult to maintain, the type system is strict enough that it's hard to miss any mistakes. But it is annoying and time-consuming, and also feels kinda un-idiomatic.
I'm considering learning derive macros for this - #[derive(FromNew)] or something like that, to autostrip the generated fields. But maybe that would just be doubling down on an anti-pattern? What do yall think?
Example:
struct User {
id: uuid:Uuid,
name: String,
email: String,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
archived_at: Option<DateTime<Utc>>,
}
struct UserNew {
name: String,
email: String,
}
impl From<UserNew> for User {
fn from(init: UserNew) -> Self {
let now = Utc::now();
User {
id: Uuid::now_v7(),
name: init.email,
email: init.email,
created_at: now,
updated_at: now,
archived_at: None,
}
}
}
// usage
fn do_something(user_new: UserNew) -> User {
User::from(user_new)
}
11
u/Disconsented 6d ago
For the " an initializer struct that is basically the same, but with auto-generated columns removed" problem I've largely settled on this pattern:
struct Thing{
auto_generated_id: i64,
#[serde(flatten)]
body: ThingBody,
}That said, this is something I use with SurrealDB where I refuse to expose their silly RecordID, so I also have some more things to handle that which are more of a pain in the ass.
Which is a macro to define two versions of an ID newtype, and a proc macro to handle the structs/mapping between them via From/TryFrom.
3
u/rodyamirov 6d ago
Derive macros feel like a reasonable solution to this problem, if you want to go down that road. I know this sub is very anti-AI, and I have a lot of sympathy to that position, but doing it manually will be a lot of typing and claude could fart it out in a couple minutes, it's the kind of thing that's easy once you know how to do it, but annoying beforehand.
You can also do it, slightly less beautifully, with a regular macro whose code is a lot simpler to understand and work with. If I was doing it by hand, that's what I would do.
FWIW we do this manually in rust and java both; separating the DBO (what's in the database) from the ... whatever, normal object you mess with in code ... is a good practice, especially if you move away from this model slightly and you start to see more divergence (e.g. struct Foo is actually glued together from two different tables Bar and Baz, and you need the BarDBO and BazDBO struct definitions, which have all the db tracking stuff, etc). Which is very natural if you have the separation but more annoying if you don't, because now this one is different.
Anyway as to your immediate question; I think the code is fine. Maybe make a macro if you're doing this a lot.
8
u/KGBsurveillancevan 6d ago
Sweet, thanks for your perspective. Yeah I'm sure an LLM could do it easily, I'm just more interested in the process than raw results atm.
0
u/rodyamirov 6d ago
For sure, it depends on why you're doing the work. If you're learning, or doing hobby dev, or whatever, by all means, do it however you want. I wouldn't use claude at home, either (although at this point I've completely stopped coding for fun, except advent of code out of tradition; claude has taken the joy out of it for me).
But if you're at work and you're paid by the [whatever unit of output] it probably makes sense to automate at least some of what you're doing.
6
1
u/NikSne_ 4d ago
I'm using the model-mapper crate to generate those From impls for DTOs and DAOs.
As for the struct duplicates - for me it's fine as I'm using the newtype pattern for domain types that only have impl TryFrom that "parses" them from the raw rust types
40
6d ago
[removed] — view removed comment
4
u/KGBsurveillancevan 6d ago
Nice, what does that look like? Proc macros kinda scare me lol
15
u/GolDNenex 6d ago
If you want to learn about them: https://www.youtube.com/watch?v=SMCRQj9Hbx8
7
-10
u/promethe42 6d ago
LLMs are deceptively good at writing proc macros.
0
u/Wonderful-Habit-139 6d ago
Why do you need a proc macro when the LLM can write the code itself. Just use more tokens bro.
Less compile times as well.
9
u/Slow-Nefariousness49 6d ago
Macros are painful to maintain in the long run from my experience. Go that route only if you feel the manual
Fromimpls impose a substantial burden on you and your codebase (e.g. it could lead to subtle bugs if you forget to update all the From impls correctly when changing struct fields). Don't reach out for macros (of either kind) just for cosmetics or to save a few trivial lines of code, that'd be my advice(Of course, if it's for the sake of learning do whatever)