immutable_seq/
lazy.rs

1// This code is based on code from Jonathan Reem's rust-lazy library (https://github.com/reem/rust-lazy)
2
3use std::ops::Deref;
4use std::cell::UnsafeCell;
5use std::rc::Rc;
6use std::mem;
7use std::fmt;
8
9use self::Inner::{Evaluated, EvaluationInProgress, Unevaluated, Redirect};
10
11/// Helper macro for writing lazy expressions
12///
13/// ```rust,ignore
14/// #[macro_use] extern crate immutable_seq;
15/// # use immutable_seq::lazy::value;
16/// # fn main() {
17/// let thunk = lazy!{
18///     println!("Evaluated!");
19///     value(7u32)
20/// };
21/// assert_eq!(*thunk, 7u32);
22/// # }
23/// ```
24#[doc(hidden)]
25#[macro_export]
26macro_rules! lazy {
27    ($($e: stmt);*) => {
28        $crate::lazy::Thunk::new(move || { $($e);* })
29    }
30}
31
32#[doc(hidden)]
33#[macro_export]
34macro_rules! lazy_val {
35    ($($e: stmt);*) => {
36        $crate::lazy::Thunk::new(move || { value({$($e);*}) })
37    }
38}
39
40#[doc(hidden)]
41#[macro_export]
42macro_rules! lazy_redirect {
43    ($($e: stmt);*) => {
44        $crate::lazy::Thunk::new(move || { redirect({$($e);*}) })
45    }
46}
47
48pub fn strict<T>(v: T) -> Thunk<T> {
49    Thunk::evaluated(v)
50}
51
52pub fn redirect<T>(t: Thunk<T>) -> ThunkResult<T> {
53    ThunkResult::Redirect(t)
54}
55
56pub fn value<T>(v: T) -> ThunkResult<T> {
57    ThunkResult::Value(v)
58}
59
60/// A sometimes-cleaner name for a lazily evaluated value.
61pub type Lazy<T> = Thunk<T>;
62
63/// A lazily evaluated value.
64pub struct Thunk<T> (UnsafeCell<Rc<UnsafeCell<Inner<T>>>>);
65
66impl<T> Thunk<T> {
67    /// Create a lazily evaluated value from a proc that returns that value.
68    ///
69    /// You can construct Thunk's manually using this, but the lazy! macro
70    /// is preferred.
71    ///
72    /// ```rust,ignore
73    /// # use immutable_seq::lazy::{Thunk, value};
74    /// let expensive = Thunk::new(|| { println!("Evaluated!"); value(7u32) });
75    /// assert_eq!(*expensive, 7u32); // "Evaluated!" gets printed here.
76    /// assert_eq!(*expensive, 7u32); // Nothing printed.
77    /// ```
78    pub fn new<F>(producer: F) -> Thunk<T>
79    where F: FnOnce() -> ThunkResult<T> + 'static {
80        Thunk(UnsafeCell::new(Rc::new(UnsafeCell::new(Unevaluated(Producer::new(producer))))))
81    }
82
83    /// Create a new, evaluated, thunk from a value.
84    pub fn evaluated(val: T) -> Thunk<T> {
85        Thunk(UnsafeCell::new(Rc::new(UnsafeCell::new(Evaluated(val)))))
86    }
87
88    /// Force evaluation of a thunk.
89    pub fn force(&self) {
90        loop {
91            match *self.inner() {
92                Evaluated(_) => return,
93                EvaluationInProgress => {
94                    panic!("Thunk::force called recursively. (A Thunk tried to force itself while trying to force itself).")
95                },
96                Redirect(ref t) => {
97                    self.redirect(t.clone());
98                    continue;
99                },
100                Unevaluated(_) => ()
101            };
102            break;
103        }
104
105        match mem::replace(self.inner(), EvaluationInProgress) {
106            Unevaluated(producer) => {
107                *self.inner() = EvaluationInProgress;
108                match producer.invoke() {
109                    ThunkResult::Value(x) =>
110                        *self.inner() = Evaluated(x),
111                    ThunkResult::Redirect(t) => {
112                        t.force();
113                        *self.inner() = Redirect(t.clone());
114                        self.redirect(t);
115                    }
116                }
117            }
118            _ => {
119                let x = 42;
120                println!("thats not good {}",x);
121                // unsafe { debug_unreachable!() }   
122            }
123        }
124    }
125
126    fn inner(&self) -> &mut Inner<T> {
127        match *self {
128            Thunk(ref cell) => unsafe {
129                &mut *(**cell.get()).get()
130            }
131        }
132    }
133
134    fn rc(&self) -> &mut Rc<UnsafeCell<Inner<T>>> {
135        match *self {
136            Thunk(ref cell) => unsafe {
137                &mut *cell.get()
138            }
139        }
140    }
141
142    fn redirect(&self, t: Thunk<T>) {
143        *self.rc() = t.rc().clone();
144    }
145}
146
147impl<T> Deref for Thunk<T> {
148    type Target = T;
149
150    fn deref(&self) -> &T {
151        self.force();
152        match *self.inner()  {
153            Evaluated(ref val) => val,
154            _ => unreachable!(),
155        }
156    }
157}
158
159impl<T> Clone for Thunk<T> {
160    fn clone(&self) -> Thunk<T> {
161        Thunk(UnsafeCell::new(self.rc().clone()))
162    }
163}
164
165impl<T> fmt::Debug for Thunk<T>
166    where T: fmt::Debug
167{
168    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169        f.debug_tuple("Thunk")
170            .field(self.inner())
171            .finish()
172    }
173}
174
175/// Represents the two possible things a `Thunk<T>` can return: either a `T` value, or another `Thunk<T>`.
176#[derive(Debug)]
177pub enum ThunkResult<T> {
178    Value(T),
179    Redirect(Thunk<T>)
180}
181
182struct Producer<T> {
183    inner: Box<Invoke<T>>
184}
185
186impl<T> fmt::Debug for Producer<T> {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        write!(f, "Producer{{...}}")
189    }
190}
191
192impl<T> Producer<T> {
193    fn new<F: FnOnce() -> T + 'static>(f: F) -> Producer<T> {
194        Producer {
195            inner: Box::new(move || {
196                f()
197            }) as Box<Invoke<T>>
198        }
199    }
200
201    fn invoke(self) -> T {
202        self.inner.invoke()
203    }
204}
205
206#[derive(Debug)]
207enum Inner<T> {
208    Evaluated(T),
209    EvaluationInProgress,
210    Unevaluated(Producer<ThunkResult<T>>),
211    Redirect(Thunk<T>),
212}
213
214#[doc(hidden)]
215pub trait Invoke<T> {
216    fn invoke(self: Box<Self>) -> T;
217}
218
219impl<T, F> Invoke<T> for F
220    where F: FnOnce() -> T
221{
222    fn invoke(self: Box<F>) -> T {
223        let f = *self;
224        f()
225    }
226}
227
228#[cfg(test)]
229mod test {
230    use super::{Thunk, value, redirect, strict};
231    use std::sync::{Arc, Mutex};
232    use std::thread;
233
234    #[test]
235    fn test_thunk_should_evaluate_when_accessed() {
236        let val = lazy!(value(7));
237        assert_eq!(*val, 7);
238    }
239
240    #[test]
241    fn test_thunk_should_evaluate_through_redirect() {
242        let val = lazy!(redirect(lazy!(value(7))));
243        assert_eq!(*val, 7);
244    }
245
246    #[test]
247    fn test_thunk_should_evaluate_just_once() {
248        let counter = Arc::new(Mutex::new(0));
249        let counter_clone = counter.clone();
250        let val = lazy!({
251            let mut data = counter.lock().unwrap();
252            *data += 1;
253            value(())
254        });
255        *val;
256        *val;
257        assert_eq!(*counter_clone.lock().unwrap(), 1);
258    }
259
260    #[test]
261    fn test_thunk_should_not_evaluate_if_not_accessed() {
262        let counter = Arc::new(Mutex::new(0));
263        let counter_clone = counter.clone();
264        let _val = lazy!({
265            let mut data = counter.lock().unwrap();
266            *data += 1;
267            value(())
268        });
269        assert_eq!(*counter_clone.lock().unwrap(), 0);
270    }
271
272    #[test]
273    fn test_strict_should_produce_already_evaluated_thunk() {
274        let x = strict(10);
275        assert_eq!(*x, 10);
276    }
277
278    #[test]
279    fn test_drop_internal_data_just_once() {
280        let counter = Arc::new(Mutex::new(0));
281        let counter_clone = counter.clone();
282        let result = thread::spawn(move || {
283            let value = Dropper(counter_clone);
284            let t = Thunk::<()>::new(move || {
285                // Get a reference so value is captured.
286                let _x = &value;
287
288                panic!("Muahahahah")
289            });
290            t.force();
291        }).join();
292
293        match result {
294            Err(_) => {
295                assert_eq!(*counter.lock().unwrap(), 1);
296            },
297            _ => panic!("Unexpected success in spawned task.")
298        }
299    }
300
301    struct Dropper(Arc<Mutex<u64>>);
302
303    impl Drop for Dropper {
304        fn drop(&mut self) {
305            let Dropper(ref count) = *self;
306            *count.lock().unwrap() += 1;
307        }
308    }
309}