Scope guard is a practical usage of RAII (Resource Acquisition Is Initialization) to avoid resource leak, and stated scope guard is a more flexible RAII pattern for stated resource management, especially when dealing with error handling.
Let's consider the following situation: in a setup routine, the resources shall be reverted if any step failed, while if the whole steps succeed, the resources shall be preserved (not reverted) even after the program exits. For example:
fn setup() -> anyhow::Result<()> {
let log_dir = LogDir::create()?;
let user_account = UserAccount::create().inspect_err(|_| {
delete_log_dir(log_dir);
})?;
let network = UserNetwork::create().inspect_err(|_|) {
delete_user_account(user_account);
delete_log_dir(log_dir);
}?;
Ok(())
}
In this situation, the traditional scope guard (RAII) cannot work as expected, since resources like logdir
need to always be deleted except all things go right. This problem can be further expanded as stated resource management, i.e., the resource is managed differently when dropped according to the state of function. For logdir
, the state is either AllThingsGoRight
or SomethingWrong
. If SomethingWrong
, we shall delete the logdir
, and when AllThingsGoRight
, we don't need to delete the logdir
. Now, what can we do if there are many states and many resources to deal with?
To solve the stated resource management, we can use stated-scope-guard
crate like this:
use stated_scope_guard::ScopeGuard;
struct Resource;
impl Resource { fn new() -> Self { Self }}
// Define the state enumerate
enum State {
State1,
State2,
// ...
}
fn setup() {
// The resource guard can be dereferenced into the resource passed in,
// the callback will be called when resource_guard is dropped. The callback
// is expected to deal with resource differently according to the state.
let mut resource_guard = ScopeGuard::new(Resource::new(), State::State1, |res, state| {
match state {
State::State1 => { /* do something with res */ },
State::State2 => { /* do something else with res */ },
// ...
}
});
// do something may throw.
// ...
// When throwed, the resource guard will deal with res with state State1
resource_guard.set_state(State::State2);
// After this, when resource guard leaves its scope, the resource will be
// dealt with state State2
}
The third argument passed to ScopeGuard
is a callback which will be called when the scope guard is dropped. It takes current resource and state as parameter, and is expected to deal with the resource according to the state. When the state needs to be changed, we can use set_state
to do so.