1 unstable release
0.1.0 | May 2, 2023 |
---|
#2808 in Rust patterns
17KB
328 lines
Rewind
Its rewind time babeee
This crate contains utilities to help with developing APIs with strong exception guarantees. Basically, if the function fails in some way then it should be like the function was never called.
In languages such as C# and Java this can be done with finally
blocks. Rust currently however
has no way to "catch" a ?
return outside of putting it in a lambda, and anyway sometimes you want
finer grain control than entire blocks. For example, when you have multiple operations that depend on
the previous one block statements are quite unwieldy:
#[derive(Default)]
struct Stack<T> {
els: Vec<T>,
}
impl <T> Stack<T> {
pub fn pop(&mut self) -> Result<T, ()> {
self.els.pop().ok_or(())
}
pub fn get(&self, index: usize) -> Result<&T, ()> {
self.els.get(index).ok_or(())
}
pub fn push(&mut self, el: T) {
self.els.push(el);
}
}
fn may_fail() -> Result<(), ()> { Err(()) }
let mut s = Stack::<i32>::default();
let result = (|| {
s.push(4);
s.push(5);
let value = s.pop()?;
may_fail()?;
println!("{}", value);
Ok::<(), ()>(())
})();
if result.is_err() {
s.push(4);
}
assert_eq!(s.els, vec![4, 4]); // uh oh
As indicated by the comment, the above code will push 4
to the stack twice. Although the example is simple, this
mistake is quite easy to make in code where the side effects are complex. Now lets look at this code using rewind
:
use rewind::Atom;
#[derive(Default)]
struct Stack<T> {
els: Vec<T>,
}
impl<T> Stack<T> {
pub fn pop(&mut self) -> Result<T, ()> {
self.els.pop().ok_or(())
}
pub fn push(&mut self, el: T) {
self.els.push(el);
}
}
fn may_fail() -> Result<(), ()> {
Err(())
}
let mut s = rewind::encase(Stack::<i32>::default());
let result = (|| {
s.push(4);
s.push(5);
let value = s.peel_mut(
|s| s.pop(),
|s, v| {
if let Ok(v) = v {
s.push(v);
}
},
);
may_fail()?;
println!("{}", value.decay()?);
Ok::<(), ()>(())
})();
assert!(result.is_err());
assert_eq!(s.els, vec![4, 5]);