That seems like an inside-out way of doing it to me. I would schedule work onto the transaction struct and make it ultimately responsible for if it should roll back the work or keep it committed.
let mut tx = Transaction::new();
dofoo(&mut tx)?;
dobar(&mut tx)?;
tx.commit();
There is some overhead to boxing the rollback functions for dofoo/dobar into the transaction object, but it's far less error prone (or maybe you can avoid the boxing by encoding all the rollback operations at the type level: less ergonomic but not by much).