this post was submitted on 10 Nov 2024
1 points (100.0% liked)

Rust

5989 readers
4 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

[email protected]

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS
 

In order to share the running transaction into a DAO style data management class, I have wrapped the transaction in an Arc and pass it into the DAO.

The issue is, once the transaction is in there I cannot call commit() on it because it cannot be moved out of the Arc anymore, as the commit requires a mut self.

Any ideas on how to work around this?

top 14 comments
sorted by: hot top controversial new old
[–] [email protected] 0 points 5 days ago (1 children)

This screams of XY problem. You've gotten a new problem from using this method and you're asking for help for that, but probably there is an underlying better solution that solves your actual use case without running into this problem at all.

[–] [email protected] 0 points 5 days ago (1 children)

So your answer is "you are doing it wrong".

Thank you, very helpful πŸ˜‰

[–] [email protected] 0 points 5 days ago (1 children)

That's not what I said. Read about the XY problem and then come back and explain what you actually want to achieve, and give some more information like code examples.

[–] [email protected] 0 points 5 days ago (1 children)

Well. I did read up on the "XY" problem and apparently you assume that what I want to do is somehow bad practice.

To elaborate more on the problem: I am writing an axum backend which (like other backends) needs to do stuff in the database. As some endpoints update the database (and sometimes over multiple sql statements) I want to pass around the transaction as this embodies the connection I am using to update the database.

To separate the axum stuff (parameters, urls and whatnot) from the actual database logic, I've first pulled out all the database interactions into separate functions. Because those functions are logically groups (e.g. stuff happening with invoices, others with contacts etc), I thought it was a good idea to create a "dao" struct (and agreed: my OO brain kicked in here which might be debatable). This would group the interactions for each logical domain into a short-lived data access struct.

To prevent passing around the transaction/connection, i wanted to pass that along during construction, so the functions in the DAO could just access the connection when needed.

Non "OO" would be fine to be honest (just having all the DAO as functions in a separate package).

Sorry, no code, not at the computer atm.

(And yes, I am aware that rust is not OO, put away the pitchforks please πŸ™)

[–] [email protected] 0 points 5 days ago (1 children)

Thanks for explaining further, it's a lot clearer now what you want to do. And no, I don't think this DAO thing is idiomatic for Rust and you probably don't want to do it like that. I'm not familiar with the pattern though, I'm not too much into OOP myself.

Anyways, I've worked a lot with axum and sqlx before so I can tell you what I'd do.

I am writing an axum backend which (like other backends) needs to do stuff in the database. As some endpoints update the database (and sometimes over multiple sql statements) I want to pass around the transaction as this embodies the connection I am using to update the database.

This makes sense. You just want a database connection pool (sqlx provides this) in your axum state so your handlers can get connections to the database.

To separate the axum stuff (parameters, urls and whatnot) from the actual database logic, I’ve first pulled out all the database interactions into separate functions. Because those functions are logically groups (e.g. stuff happening with invoices, others with contacts etc), I thought it was a good idea to create a β€œdao” struct (and agreed: my OO brain kicked in here which might be debatable). This would group the interactions for each logical domain into a short-lived data access struct.

Again, not sure what this DAO struct actually entails, but what I would do and have done in the past is just do exactly what you said before: "I want to pass around the transaction". So I would make my functions take the Transaction struct from sqlx (IIRC it has some type parameters and a life time but you can use a type alias to make it less verbose) and then I would just use that transaction in the function to call SQL. If you have a function that needs access to the database but doesn't need a transaction, you can just use a plain connection instead of a transaction.

To prevent passing around the transaction/connection, i wanted to pass that along during construction

I'm not sure what you mean with "pass along during construction" but in any case, why do you want to avoid passing the transaction/connection? I feel like that is exactly what you should do. That is what you need to do anyway. Rust favours explicitness and you need to pass the transaction/connection to functions for them to use it, so just pass it.

[–] [email protected] 0 points 5 days ago (1 children)

It is now quite clear that I have to let go of OO paradigms. Maybe the package approach is perfectly fine.

Thank you for the insights and useful answer!

I have the idea that I am still only scratching the surface of rust, although I seem to manage it better every day.

As long as I leave my OO hat on the rack πŸ˜‰

[–] [email protected] 0 points 5 days ago (1 children)

Definitely let go. Rust has some OOP features, but it's mostly just the OOP idea of interfaces, which Rust models with traits. You can also do dynamic dispatch, which is another OOP feature, but you should almost never use this in Rust unless you absolutely have to. Then there's encapsulation which is hugely important in Rust too, but yea outside of that kind of thing, I don't think OOP patterns are too useful. Honestly, if you ask me, many of these "OOP patterns" are really just solving problems that OOP causes in the first place.

Feel free to ask any other questions.

[–] [email protected] 0 points 5 days ago

Thank you again and I will.

[–] [email protected] 0 points 5 days ago (1 children)

Transactions should be short lived, they block data on the database side from acessing those tables or rows. Best to not jole onto a transaction that long and instead gather your data first or rethink your access patterns to your data base.

But arc does give you a try_unwrap which returns the inner type if there is only one strong copy left. And mutex gives you an into_inner to move out of it. But really transactions should not be held for a long period of time.

[–] [email protected] 0 points 5 days ago (1 children)

Seems like the into_inner is the way (i see other references to it).

And yes, transactions should be short-lived, this is just about delegating it to the responsible component.

[–] [email protected] 0 points 5 days ago (1 children)

Not sure why you need an arc mutex to delegate it to the responsible component. Seems like the type of thing that should not cross thread boundaries nor be cloned multiple times.

[–] [email protected] 0 points 5 days ago (1 children)

Not talking threads here, more Tokio tasks.

[–] [email protected] 0 points 5 days ago

You appear to be generally confused.

If you're using the multi-threaded work-stealing tokio runtime (the default), you are "talking threads". And if you aren't, Arc and Mutex would be useless, irregardless of whether you're doing it right or wrong.

[–] [email protected] 0 points 5 days ago

Make sure there’s only one strong reference and call Arc::into_inner to move it out of the Arc. Same can be done with Mutex::into_inner to move the transaction out of the mutex