evobench_tools/utillib/
linear.rs

1//! Run-time "linear types"
2//!
3//! They panic in the `Drop` implementation in debug builds,
4//! optionally only warning in release builds (depending on `fatal`
5//! runtime value in the type).
6//!
7//! Attempts at using the idea of panicking in const context does not
8//! work in practice (it appears that drop templates are instantiated
9//! before being optimized away, hence e.g. returning them in
10//! `Result::Ok` is not possible since it apparently unconditionally
11//! instantiates the drop for Result which instantiates the drop for
12//! the Ok value even if never used, but I did not manage to analyze
13//! the binary to detect drop use after the optimizer either).
14
15//! Only token types are supported: embedding such a token type inside
16//! a larger data structure makes the larger data structure run-time
17//! checked for linearity, too. That type then has to provide a moving
18//! method or function that throws away the linear token by calling
19//! `bury()` on it. With that approach, no wrapper around the
20//! containing data structure is needed, which might be cleaner?
21
22//! Original idea and partially code came from
23//! <https://jack.wrenn.fyi/blog/undroppable/> and
24//! <https://geo-ant.github.io/blog/2024/rust-linear-types-use-once/>,
25//! but again, doesn't appear to work in practice. There are also some
26//! other crates going the runtime route, maybe the most-used one
27//! being <https://crates.io/crates/drop_bomb>.
28
29//! Calling `std::mem::leak` (even on a containing data structure)
30//! by-passes the linearity.  It is recommended to only ever use the
31//! `bury()` method to get rid of a linear token, and reserve the use
32//! of `std::mem::forget` for other purposes, so that searching the
33//! code base for it will still just show up actual potential memory
34//! leaks as well as potential improper bypasses of the linearity:
35//! `forget` on an enclosing data structure bypasses the check and can
36//! be done for any data type; whereas `bury()` is only available to
37//! the code declaring the linear token type, hence under the control
38//! of the library.
39
40use std::marker::PhantomData;
41
42/// A type that cannot be dropped.
43#[must_use]
44pub struct UndroppableWithin<T, IN> {
45    fatal: bool,
46    inner: PhantomData<(T, IN)>,
47}
48
49impl<T, IN> UndroppableWithin<T, IN> {
50    pub fn new(fatal: bool) -> Self {
51        let inner = PhantomData;
52        Self { fatal, inner }
53    }
54}
55
56impl<T, IN> Drop for UndroppableWithin<T, IN> {
57    fn drop(&mut self) {
58        let is_debug;
59        #[cfg(not(debug_assertions))]
60        {
61            use crate::utillib::bool_env::get_env_bool;
62
63            is_debug = get_env_bool("DEBUG_LINEAR", Some(false))
64                .expect("no invalid DEBUG_LINEAR variable");
65        }
66        #[cfg(debug_assertions)]
67        {
68            is_debug = true;
69        }
70        if is_debug || self.fatal {
71            panic!(
72                "attempt to Drop an instance of the linear type {} \
73                 contained inside the type {}. \
74                 Instances of the latter type need to be passed to a cleanup function, \
75                 which itself must call `bury()` on the instance of the first type.",
76                std::any::type_name::<T>(),
77                std::any::type_name::<IN>(),
78            );
79        } else {
80            crate::warn!(
81                "WARNING: attempt to Drop an instance of the linear type {} \
82                 contained inside the type {}. \
83                 Instances of the latter type need to be passed to a cleanup function, \
84                 which itself must call `bury()` on the instance of the first type.",
85                std::any::type_name::<T>(),
86                std::any::type_name::<IN>(),
87            );
88        }
89    }
90}
91
92/// Create a new token type `$T` that is linear, i.e. whose instances
93/// are undroppable, meant to be embedded in the type `$FOR`. `$FOR`
94/// is reported as the type that is being dropped, when a `$T` is
95/// dropped! Call `$T::new()` to create an instance, and call `bury()`
96/// to get rid of it, preferably inside a method of `$FOR` that is
97/// consuming the `$FOR` object and cleans it up.
98#[macro_export]
99macro_rules! def_linear {
100    { $T:tt in $FOR:ty } => {
101        #[must_use]
102        struct $T($crate::utillib::linear::UndroppableWithin<$T, $FOR>);
103
104        impl $T {
105            fn new(fatal: bool) -> Self {
106                Self($crate::utillib::linear::UndroppableWithin::new(fatal))
107            }
108
109            pub fn bury(self) {
110                std::mem::forget(self.0)
111            }
112        }
113    }
114}
115
116#[test]
117fn t_size() {
118    assert_eq!(std::mem::size_of::<UndroppableWithin<u32, bool>>(), 1);
119    #[allow(unused)]
120    struct Bar(Foo);
121    def_linear!(Foo in Bar);
122    assert_eq!(std::mem::size_of::<Foo>(), 1);
123    assert_eq!(std::mem::size_of::<Bar>(), 1);
124    let x = Foo::new(true);
125    x.bury();
126}