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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
//! The `breaktarget` module defines one type: The `BreakTarget` type. A //! value of this type may be obtained by invoking `BreakTarget::deploy`. //! //! `BreakTarget::deploy` takes a lambda, which is passed a reference to a //! `BreakTarget`. This object defines a single method, `break_with`, which //! takes a value of type `T` and returns control flow to the site of the //! `BreakTarget::deploy` call, producing the value as the result. If the lambda //! exits normally, it must also produce a value of type `T`, which is produced //! as the result of the `BreakTarget::deploy` call. //! //! # Important Notes //! //! The nonlocal breaking is implemented using a panic to unwind the stack. Any //! open Mutexes which are closed by this unwinding will be poisoned, among with //! other unwinding specific effects. //! //! _If `panic = abort` is enabled, calls to `break_with` will abort the program_ //! //! # Examples //! //! ``` //! use breaktarget::BreakTarget; //! //! let result = BreakTarget::deploy(|target| { //! // ... Some logic here //! target.break_with(10i32); //! // ... Some logic here //! }); //! assert_eq!(result, 10i32); //! ``` //! //! ``` //! use breaktarget::BreakTarget; //! //! fn some_function(target: &BreakTarget<i32>, cond: bool) { //! if cond { //! target.break_with(10); //! } //! } //! //! let result1 = BreakTarget::deploy(|target| { //! some_function(target, false); //! 20 //! }); //! assert_eq!(result1, 20); //! //! let result2 = BreakTarget::deploy(|target| { //! some_function(target, true); //! 20 //! }); //! assert_eq!(result2, 10); //! ``` use std::panic; use std::cell::RefCell; /// A BreakRequest is a dummy zero-sized-type. It's heap address is used to /// identify which BreakTarget we are breaking towards. struct BreakRequest; /// This object represents the target stack frame which we will unwind toward /// when the break_with method is invoked. The value which we are breaking with /// will be stored within the BreakTarget to be returned when control flow /// resumes. #[derive(Debug)] pub struct BreakTarget<T>(RefCell<Option<T>>); impl<T> BreakTarget<T> { /// Deploy a break target. The target will be passed by reference to the /// argument closure. The BreakTarget object provides a single `break_with` /// method, which can be invoked to halt execution and return control to the /// deployment site. If the `break_with` function was not invoked, the /// return value of the closure will instead be produced. pub fn deploy<F>(func: F) -> T where F: FnOnce(&BreakTarget<T>) -> T { // A place for storing the information if the function aborts during its // execution. The address of this local is also used as a marker value // for the panic value when break_with is called, allowing us to resume // without parforming somewhat expensive downcasts. let target = BreakTarget(RefCell::new(None)); // Run the logic, catching any panics triggered match panic::catch_unwind(panic::AssertUnwindSafe(|| func(&target))) { Ok(v) => v, Err(panic_val) => { if let Some(panic_ptr) = panic_val.downcast_ref::<BreakRequest>() { // Check if the panic we got back has a data pointer which // refers to our break target. If it does, it was triggered // by our break_with function. if panic_ptr as *const _ as *const Self == &target as *const _ { return target.0.into_inner().unwrap(); } } panic::resume_unwind(panic_val); } } } /// Aborts the current function, returning control to the BreakTarget's /// deploy point. The argument to this method will be the return value of /// the deploy method. pub fn break_with(&self, data: T) -> ! { // Record the information in the continuation object *self.0.borrow_mut() = Some(data); // Create an unwind sentinel object. Use our address as the address for // the zero sized type BreakRequest such that we can communicate that // we are the Continuation which is being triggered, while not breaking // anything, as BreakRequest won't actually allocate any memory on the // heap, and thus the box destructor will be a no-op. let unwind_box: Box<BreakRequest> = unsafe { Box::from_raw(self as *const Self as *mut Self as *mut BreakRequest) }; // Use the resume_unwind function to unwind rather than panic! such that // the object isn't double-boxed, panic::resume_unwind(unwind_box); } } #[cfg(test)] mod tests { use super::BreakTarget; use std::panic; #[test] fn basic_use() { let mut before = false; let res = BreakTarget::deploy(|t| { before = true; t.break_with(1); }); assert_eq!(res, 1); assert!(before); } #[test] fn unwind_to_outer() { let res = BreakTarget::deploy(|t| { BreakTarget::deploy(|_| t.break_with(1)); unreachable!(); }); assert_eq!(res, 1); } #[test] fn propagate_panic() { // Ensure that panics produced within a BreakTarget::deploy are propagated to caller if let Err(e) = panic::catch_unwind(|| BreakTarget::deploy(|_| panic!(1u32))) { assert_eq!(e.downcast_ref::<u32>(), Some(&1u32)); } else { assert!(false, "should panic"); } } }