shadow_shim_helper_rs/
explicit_drop.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/// Trait for a type that provides an explicit method for dropping its value.
///
/// Unlike the `Drop` trait, this traits method:
/// * Can take a parameter
/// * Can return something
/// * Consumes the value (instead of taking a `&mut`)
///
/// Unfortunately there is no built-in way to *ensure* a type is explicitly
/// dropped. One workaround is for a type to also implement `Drop`, and have
/// that implementation validate that `ExplicitDrop::explicit_drop` was called.
pub trait ExplicitDrop {
    type ExplicitDropParam;
    type ExplicitDropResult;
    fn explicit_drop(self, param: &Self::ExplicitDropParam) -> Self::ExplicitDropResult;
}

/// Wrapper that uses a provided function to drop the inner value.
///
/// This is helpful for working with locals that must be explicitly dropped, without
/// having to explicitly do so at every potential exit point of the current function.
///
/// ```
/// # use shadow_shim_helper_rs::explicit_drop::{ExplicitDrop, ExplicitDropper};
/// # use shadow_shim_helper_rs::rootedcell::Root;
/// # use shadow_shim_helper_rs::rootedcell::rc::RootedRc;
/// # use std::string::String;
/// # use std::error::Error;
/// fn validate_and_rc(root: &Root, s: String) -> Result<RootedRc<String>, Box<dyn Error>> {
///   let rc_string = ExplicitDropper::new(
///     RootedRc::new(root, s),
///     |value| value.explicit_drop(root));
///
///   if !rc_string.starts_with("x") {
///     // `ExplicitDropper` will call the provided closure, safely dropping the RootedRc.
///     return Err("bad prefix".into());
///   }
///
///   // We extract the value from the dropper at the point where we transfer ownership;
///   // in this case the closure is never called.
///   return Ok(rc_string.into_value())
/// }
/// ```
pub struct ExplicitDropper<DropFn, Value>
where
    DropFn: FnOnce(Value),
{
    internal: Option<(DropFn, Value)>,
}

impl<DropFn, Value> ExplicitDropper<DropFn, Value>
where
    DropFn: FnOnce(Value),
{
    /// Create a wrapped value
    pub fn new(value: Value, dropper: DropFn) -> Self {
        Self {
            internal: Some((dropper, value)),
        }
    }

    /// Unwrap the value, discarding the dropper.
    pub fn into_value(mut self) -> Value {
        let (_drop_fn, value) = self.internal.take().unwrap();
        value
    }
}

impl<DropFn, Value> Drop for ExplicitDropper<DropFn, Value>
where
    DropFn: FnOnce(Value),
{
    fn drop(&mut self) {
        if let Some((drop_fn, value)) = self.internal.take() {
            drop_fn(value)
        }
    }
}

impl<DropFn, Value> std::ops::Deref for ExplicitDropper<DropFn, Value>
where
    DropFn: FnOnce(Value),
{
    type Target = Value;

    fn deref(&self) -> &Self::Target {
        let (_drop_fn, value) = self.internal.as_ref().unwrap();
        value
    }
}

impl<DropFn, Value> std::ops::DerefMut for ExplicitDropper<DropFn, Value>
where
    DropFn: FnOnce(Value),
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        let (_drop_fn, value) = self.internal.as_mut().unwrap();
        value
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_explicit_dropper_explicitly_drops() {
        let mut dropper_called = false;
        let x = ExplicitDropper::new(42, |val| {
            assert_eq!(val, 42);
            dropper_called = true;
        });
        drop(x);
        assert!(dropper_called);
    }

    #[test]
    fn test_explicit_dropper_into_value() {
        let mut dropper_called = false;
        let x = ExplicitDropper::new(42, |_| dropper_called = true);
        assert_eq!(x.into_value(), 42);
        assert!(!dropper_called);
    }
}