├── .gitignore ├── .travis.yml ├── Cargo.toml ├── README.md ├── src ├── lib.rs ├── single.rs └── sync.rs └── tests ├── single.rs └── sync.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *# 4 | *.o 5 | *.so 6 | *.swp 7 | *.dylib 8 | *.dSYM 9 | *.dll 10 | *.rlib 11 | *.dummy 12 | *.exe 13 | *-test 14 | /doc/ 15 | /target/ 16 | /examples/* 17 | !/examples/*.rs 18 | Cargo.lock 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "lazy" 4 | version = "0.5.3" 5 | authors = ["Jonathan Reem "] 6 | repository = "https://github.com/reem/rust-lazy" 7 | description = "Lazily evaluated types and macros." 8 | readme = "README.md" 9 | license = "MIT" 10 | 11 | [dependencies] 12 | debug_unreachable = "*" 13 | oncemutex = "*" 14 | 15 | [dev-dependencies] 16 | stainless = "*" 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy 2 | 3 | > Lazy evaluation in Rust. 4 | 5 | ## Example 6 | 7 | ```rust 8 | fn expensive() -> i32 { 9 | println!("I am only evaluated once!"); 7 10 | } 11 | 12 | fn main() { 13 | let a = lazy!(expensive()); 14 | 15 | // Thunks are just smart pointers! 16 | assert_eq!(*a, 7); // "I am only evaluated once." is printed here 17 | 18 | let b = [*a, *a]; // Nothing is printed. 19 | assert_eq!(b, [7, 7]); 20 | } 21 | ``` 22 | 23 | ## API 24 | 25 | > `lazy!($expr)` 26 | 27 | Expands to `Thunk::new(|| { $expr })` 28 | 29 | > `Thunk::new(|| -> T)` 30 | 31 | Takes an FnOnce closure, creates a delayed computation. 32 | 33 | > `Thunk::force()` 34 | 35 | Forces the evaluation of the thunk so subsequent accesses are cheap. Values are 36 | stored unboxed. 37 | 38 | > `Thunk::unwrap()` 39 | 40 | Consumes and forces the evaluation of the thunk and returns the contained 41 | value. 42 | 43 | > `Thunk::deref()` / `Thunk::deref_mut()` 44 | 45 | Gets the value out of the thunk by evaluating the closure or grabbing it 46 | from the cache. Allows you to call methods on the thunk as if it was 47 | an instance of the contained valued through auto-deref. 48 | 49 | There is also an equivalent API for `SyncThunk`, which is `Send + Sync` and 50 | usable for safe, concurrent laziness, except that they are created using 51 | `sync_lazy!` or by doing `use lazy::SyncThunk as Thunk` and using `lazy!`. 52 | 53 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs, warnings)] 2 | #![feature(core, std_misc, unsafe_destructor, optin_builtin_traits)] 3 | 4 | //! Lazy evaluation for Rust. 5 | 6 | #[macro_use(debug_unreachable)] 7 | extern crate debug_unreachable; 8 | 9 | extern crate oncemutex; 10 | 11 | /// A Thunk safe for single-threaded access. 12 | pub mod single; 13 | 14 | /// A Thunk safe for multi-threaded use. 15 | pub mod sync; 16 | 17 | mod lazy { 18 | pub use super::*; 19 | } 20 | 21 | #[macro_export] 22 | macro_rules! lazy { 23 | ($e:expr) => { 24 | $crate::single::Thunk::new(move || { $e }) 25 | } 26 | } 27 | 28 | #[macro_export] 29 | macro_rules! sync_lazy { 30 | ($e:expr) => { 31 | $crate::sync::Thunk::new(move || { $e }) 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/single.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use std::cell::UnsafeCell; 3 | use std::ptr; 4 | use std::thunk::Invoke; 5 | 6 | use self::Inner::{Evaluated, EvaluationInProgress, Unevaluated}; 7 | 8 | /// A sometimes-cleaner name for a lazily evaluated value. 9 | pub type Lazy<'a, T> = Thunk<'a, T>; 10 | 11 | /// A lazily evaluated value. 12 | pub struct Thunk<'a, T> { 13 | inner: UnsafeCell>, 14 | } 15 | 16 | impl<'a, T> !Sync for Thunk<'a, T> {} 17 | 18 | impl<'a, T> Thunk<'a, T> { 19 | /// Create a lazily evaluated value from a proc that returns that value. 20 | /// 21 | /// You can construct Thunk's manually using this, but the lazy! macro 22 | /// is preferred. 23 | /// 24 | /// ```rust 25 | /// # use lazy::single::Thunk; 26 | /// let expensive = Thunk::new(|| { println!("Evaluated!"); 7u }); 27 | /// assert_eq!(*expensive, 7u); // "Evaluated!" gets printed here. 28 | /// assert_eq!(*expensive, 7u); // Nothing printed. 29 | /// ``` 30 | pub fn new(producer: F) -> Thunk<'a, T> 31 | where F: 'a + FnOnce() -> T { 32 | Thunk { 33 | inner: UnsafeCell::new(Unevaluated(Producer::new(producer))), 34 | } 35 | } 36 | 37 | 38 | /// Create a new, evaluated, thunk from a value. 39 | pub fn evaluated<'b>(val: T) -> Thunk<'b, T> { 40 | Thunk { inner: UnsafeCell::new(Evaluated(val)) } 41 | } 42 | 43 | /// Force evaluation of a thunk. 44 | pub fn force(&self) { 45 | unsafe { 46 | match *self.inner.get() { 47 | Evaluated(_) => return, 48 | EvaluationInProgress => { 49 | panic!("Thunk::force called recursively. (A Thunk tried to force itself while trying to force itself).") 50 | }, 51 | Unevaluated(_) => () 52 | } 53 | 54 | match ptr::replace(self.inner.get(), EvaluationInProgress) { 55 | Unevaluated(producer) => *self.inner.get() = Evaluated(producer.invoke()), 56 | _ => debug_unreachable!() 57 | }; 58 | } 59 | } 60 | 61 | /// Force the evaluation of a thunk and get the value, consuming the thunk. 62 | pub fn unwrap(self) -> T { 63 | self.force(); 64 | unsafe { 65 | match self.inner.into_inner() { 66 | Evaluated(val) => { val }, 67 | _ => debug_unreachable!() 68 | } 69 | } 70 | } 71 | } 72 | 73 | struct Producer<'a, T> { 74 | inner: Box + 'a> 75 | } 76 | 77 | impl<'a,T> Producer<'a,T> { 78 | fn new T>(f: F) -> Producer<'a,T> { 79 | Producer { 80 | inner: Box::new(move |()| { 81 | f() 82 | }) as Box> 83 | } 84 | } 85 | 86 | fn invoke(self) -> T { 87 | self.inner.invoke(()) 88 | } 89 | } 90 | 91 | enum Inner<'a, T> { 92 | Evaluated(T), 93 | EvaluationInProgress, 94 | Unevaluated(Producer<'a, T>) 95 | } 96 | 97 | impl<'x, T> Deref for Thunk<'x, T> { 98 | type Target = T; 99 | 100 | fn deref<'a>(&'a self) -> &'a T { 101 | self.force(); 102 | match unsafe { &*self.inner.get() } { 103 | &Evaluated(ref val) => val, 104 | _ => unsafe { debug_unreachable!() } 105 | } 106 | } 107 | } 108 | 109 | impl<'x, T> DerefMut for Thunk<'x, T> { 110 | fn deref_mut<'a>(&'a mut self) -> &'a mut T { 111 | self.force(); 112 | match unsafe { &mut *self.inner.get() } { 113 | &mut Evaluated(ref mut val) => val, 114 | _ => unsafe { debug_unreachable!() } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | use oncemutex::OnceMutex; 3 | use std::mem; 4 | use std::thunk::Invoke; 5 | 6 | use self::Inner::{Evaluated, EvaluationInProgress, Unevaluated}; 7 | 8 | /// A sometimes cleaner name. 9 | pub type Lazy<'a,T> = Thunk<'a,T>; 10 | 11 | /// Sync, Send lazy data. 12 | pub struct Thunk<'a, T> { 13 | inner: OnceMutex> 14 | } 15 | 16 | unsafe impl<'a, T: Sync> Sync for Thunk<'a, T> {} 17 | 18 | impl<'a, T: Send + Sync> Thunk<'a, T> { 19 | /// Create a new sync thunk. 20 | /// 21 | /// You can construct Thunk's manually using this, but the 22 | /// sync_lazy! macro is preferred. 23 | /// 24 | /// ```rust 25 | /// # use lazy::sync::Thunk; 26 | /// # use std::sync::Arc; 27 | /// # use std::thread::Thread; 28 | /// let expensive = Thunk::new(|| { println!("Evaluated!"); 7u }); 29 | /// let reff = Arc::new(expensive); 30 | /// let reff_clone = reff.clone(); 31 | /// 32 | /// // Evaluated is printed sometime beneath this line. 33 | /// Thread::spawn(move || { 34 | /// assert_eq!(**reff_clone, 7u); 35 | /// }); 36 | /// assert_eq!(**reff, 7u); 37 | /// ``` 38 | pub fn new(producer: F) -> Thunk<'a, T> 39 | where F: Send + Sync + FnOnce() -> T { 40 | Thunk { 41 | inner: OnceMutex::new(Unevaluated(Producer::new(producer))) 42 | } 43 | } 44 | 45 | /// Create a new, evaluated, thunk from a value. 46 | pub fn evaluated(val: T) -> Thunk<'a, T> { 47 | let mutex = OnceMutex::new(Evaluated(val)); 48 | 49 | // Since we use the invariants of the OnceMutex later, 50 | // we have to ensure that they are upheld in this case 51 | // by using up our lock. 52 | mutex.lock(); 53 | 54 | Thunk { inner: mutex } 55 | } 56 | 57 | /// Force evaluation of a thunk. 58 | pub fn force(&self) { 59 | match self.inner.lock() { 60 | // We are the thread responsible for doing the evaluation. 61 | Some(mut lock) => { 62 | match mem::replace(&mut *lock, EvaluationInProgress) { 63 | Unevaluated(producer) => *lock = Evaluated(producer.invoke()), 64 | // Since the OnceMutex only lets us get here once, 65 | // it *must* contain Unevaluated. 66 | _ => unsafe { debug_unreachable!() } 67 | } 68 | }, 69 | // Already forced or forcing, so wait for the value 70 | // if we need to. 71 | // 72 | // Unfortunately, we do not know if this is a 73 | // recursive force, meaning this will cause a deadlock, 74 | // or if we are waiting on another thread. 75 | None => self.inner.wait() 76 | } 77 | } 78 | } 79 | 80 | impl<'a, T: Send + Sync> DerefMut for Thunk<'a, T> { 81 | fn deref_mut(&mut self) -> &mut T { 82 | self.force(); 83 | match *&mut*self.inner { 84 | // Safe because getting this &'a mut T requires &'a mut self. 85 | // 86 | // We can't use copy_mut_lifetime here because self is already 87 | // borrowed as &mut by val. 88 | Evaluated(ref mut val) => unsafe { mem::transmute(val) }, 89 | 90 | // We just forced this thunk. 91 | _ => unsafe { debug_unreachable!() } 92 | } 93 | } 94 | } 95 | 96 | impl<'a,T: Send + Sync> Deref for Thunk<'a,T> { 97 | type Target = T; 98 | 99 | fn deref(&self) -> &T { 100 | self.force(); 101 | match *self.inner { 102 | // Safe because getting this &'a T requires &'a self. 103 | Evaluated(ref val) => unsafe { mem::copy_lifetime(self, val) }, 104 | 105 | // We just forced this thunk. 106 | _ => unsafe { debug_unreachable!() } 107 | } 108 | } 109 | } 110 | 111 | struct Producer<'a,T> { 112 | inner: Box + Send + Sync + 'a> 113 | } 114 | 115 | impl<'a,T> Producer<'a, T> { 116 | fn new T>(f: F) -> Producer<'a, T> { 117 | Producer { 118 | inner: Box::new(move |()| { 119 | f() 120 | }) as Box + Send + Sync> 121 | } 122 | } 123 | 124 | fn invoke(self) -> T { 125 | self.inner.invoke(()) 126 | } 127 | } 128 | 129 | enum Inner<'a,T> { 130 | Evaluated(T), 131 | EvaluationInProgress, 132 | Unevaluated(Producer<'a,T>) 133 | } 134 | -------------------------------------------------------------------------------- /tests/single.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![plugin(stainless)] 3 | 4 | #[macro_use] 5 | extern crate lazy; 6 | 7 | pub use lazy::single::Thunk; 8 | pub use std::sync::{Arc, Mutex}; 9 | pub use std::thread; 10 | 11 | describe! thunk { 12 | it "should evaluate when accessed" { 13 | let val = lazy!(7); 14 | assert_eq!(*val, 7); 15 | } 16 | 17 | it "should evaluate just once" { 18 | let counter = Arc::new(Mutex::new(0)); 19 | let counter_clone = counter.clone(); 20 | let val = lazy!({ 21 | let mut data = counter.lock().unwrap(); 22 | *data += 1; 23 | }); 24 | *val; 25 | *val; 26 | assert_eq!(*counter_clone.lock().unwrap(), 1); 27 | } 28 | 29 | it "should not evaluate if not accessed" { 30 | let counter = Arc::new(Mutex::new(0)); 31 | let counter_clone = counter.clone(); 32 | let _val = lazy!({ 33 | let mut data = counter.lock().unwrap(); 34 | *data += 1; 35 | }); 36 | assert_eq!(*counter_clone.lock().unwrap(), 0); 37 | } 38 | 39 | describe! unwrap { 40 | it "should retrieve the value" { 41 | let val = lazy!(7); 42 | assert_eq!(val.unwrap(), 7); 43 | } 44 | } 45 | 46 | describe! evaluated { 47 | it "should produce an already evaluated thunk" { 48 | let x = Thunk::evaluated(10); 49 | assert_eq!(*x, 10); 50 | } 51 | } 52 | 53 | describe! drop { 54 | it "should drop internal data just once" { 55 | let counter = Arc::new(Mutex::new(0)); 56 | let counter_clone = counter.clone(); 57 | let result = thread::spawn(move || { 58 | let value = Dropper(counter_clone); 59 | let t = Thunk::<()>::new(move || { 60 | // Get a reference so value is captured. 61 | let _x = &value; 62 | 63 | panic!("Muahahahah") 64 | }); 65 | t.force(); 66 | }).join(); 67 | 68 | match result { 69 | Err(_) => { 70 | assert_eq!(*counter.lock().unwrap(), 1); 71 | }, 72 | _ => panic!("Unexpected success in spawned task.") 73 | } 74 | } 75 | } 76 | } 77 | 78 | pub struct Dropper(Arc>); 79 | 80 | impl Drop for Dropper { 81 | fn drop(&mut self) { 82 | let Dropper(ref count) = *self; 83 | *count.lock().unwrap() += 1; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/sync.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin, std_misc, old_io)] 2 | #![plugin(stainless)] 3 | 4 | #[macro_use] 5 | extern crate lazy; 6 | 7 | pub use lazy::sync::Thunk; 8 | pub use std::sync::{Arc, Barrier, Mutex}; 9 | pub use std::{old_io, time}; 10 | pub use std::thread; 11 | 12 | describe! sync { 13 | it "should evaluate when accessed" { 14 | let val = sync_lazy!(7); 15 | assert_eq!(*val, 7); 16 | } 17 | 18 | it "should evaluate just once" { 19 | let counter = Arc::new(Mutex::new(0)); 20 | let counter_clone = counter.clone(); 21 | let val = sync_lazy!({ 22 | let mut data = counter.lock().unwrap(); 23 | *data += 1; 24 | }); 25 | *val; 26 | *val; 27 | assert_eq!(*counter_clone.lock().unwrap(), 1); 28 | } 29 | 30 | it "should not evaluate if not accessed" { 31 | let counter = Arc::new(Mutex::new(0)); 32 | let counter_clone = counter.clone(); 33 | let _val = sync_lazy!({ 34 | let mut data = counter.lock().unwrap(); 35 | *data += 1; 36 | }); 37 | assert_eq!(*counter_clone.lock().unwrap(), 0); 38 | } 39 | 40 | it "should be send and sync" { 41 | Arc::new(sync_lazy!(0)); 42 | } 43 | 44 | it "should be safe to access while evaluating" { 45 | let data = Arc::new(sync_lazy!({ 46 | old_io::timer::sleep(time::Duration::milliseconds(50)); 47 | 5 48 | })); 49 | 50 | let data_worker = data.clone(); 51 | 52 | // Worker task. 53 | thread::spawn(move || { 54 | data_worker.force(); 55 | }); 56 | 57 | // Try to access the data while it is evaulating. 58 | assert_eq!(5, **data); 59 | } 60 | 61 | describe! evaluated { 62 | it "should create an already evaluated thunk" { 63 | let x = Thunk::evaluated(10); 64 | assert_eq!(*x, 10); 65 | } 66 | } 67 | } 68 | --------------------------------------------------------------------------------