logoalt Hacker News

simonasktoday at 8:31 AM1 replyview on HN

For context, the reason this would be really nice is that it would enable API designs that catch certain kinds of errors.

    let txn = create_transaction();
    // do something with the transaction
    txn.commit(); // consume the txn
Right now, you can't implement this API without choosing between either silently rolling back unless the user calls `commit()`, or panicking in the Drop impl for the transaction if the user didn't explicitly call either `commit()` or `rollback()`.

Your only current choice is to use closures, which are much less composable, because you need a variant for each flavor: infallible, fallible, async fallibe, etc.

    start_transaction_async(async || { /* ... */ TransactionResult::Commit });
    start_transaction_async_try(async || { /* ... */ Ok(TransactionResult::Commit });
Ick.

If instead the transaction is a must-move type, you would get a compiler error if you fail to call exactly one of either commit or rollback, and particularly you would be forced to consider what happens at every exit point (early-out via `?` no longer just forgets the transaction). Very nice.


Replies

ordutoday at 10:55 AM

> If instead the transaction is a must-move type, you would get a compiler error if you fail to call exactly one of either commit or rollback

Can you elaborate how it may work? I mean if I create a function:

fn fail_silently(txn: Transaction) {}

then the calling code would pass the compiler, but this function presumably isn't, ok. But what can make these functions to pass:

impl Transaction { pub fn commit(self) { ... } pub fn rollback(self) { ... } }

Would you need to destructure self or what?

show 2 replies