Announcing stated-scope-guard

GitHub link

crates.io link

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.

unless I'm misunderstanding your use case, but I think your first example is achievable with scopeguard:

fn setup() -> anyhow::Result<(LogDir, UserAccount, UserNetwork)> {
	let log_dir = guard(LogDir::create()?, |log_dir| {
		delete_log_dir(log_dir);
	});
	let user_account = guard(UserAccount::create()?, |user_account| {
		delete_user_account(user_account);
	});
	let user_network = guard(UserNetwork::create()?, |user_network| {
		delete_user_network(user_network);
	});
	// all done, dismiss the guards
	Ok((
		ScopeGuard::into_inner(log_dir),
		ScopeGuard::into_inner(user_account),
		ScopeGuard::into_inner(user_network),
	))
}

I don't get the point of your next example use case, what exactly do you mean by stated resource management? I've never heard such terminology before. are you referring to some variant of two-phase initialization or something? can you please elaborate more?

Thank you for your response. :slight_smile:

I think your first example is achievable with scopeguard

Yes, this code snippet does solve the problem of first example, the low-level logic is the same.

what exactly do you mean by stated resource management?

Sorry for the ambiguous terminology. What I mean is the resource release based on function state at drop time. For logdir, when it is dropped, the function state may be AllThingsGoRight or SomethingWrong, and the resource release approach is different based on the state. For logdir, there are two possible states when dropping, while for others, there may be three or more states, and my crate is used to handle this thing. This problem can also be handled by scopeguard with a local state variable, and it is the same with my way under the hood.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.