Skip to main content

fp_library/types/
try_trampoline.rs

1//! Stack-safe fallible computation type with guaranteed safety for unlimited recursion depth.
2//!
3//! Wraps [`Trampoline<Result<A, E>>`](crate::types::Trampoline) with ergonomic combinators for error handling. Provides complete stack safety for fallible computations that may recurse deeply.
4//!
5//! ### Examples
6//!
7//! ```
8//! use fp_library::types::*;
9//!
10//! let task: TryTrampoline<i32, String> =
11//! 	TryTrampoline::ok(10).map(|x| x * 2).bind(|x| TryTrampoline::ok(x + 5));
12//!
13//! assert_eq!(task.evaluate(), Ok(25));
14//! ```
15
16#[fp_macros::document_module]
17mod inner {
18	use {
19		crate::{
20			classes::{
21				Deferrable,
22				LazyConfig,
23				Monoid,
24				Semigroup,
25				TryLazyConfig,
26			},
27			types::{
28				Lazy,
29				Thunk,
30				Trampoline,
31				TryLazy,
32			},
33		},
34		core::ops::ControlFlow,
35		fp_macros::*,
36		std::fmt,
37	};
38
39	/// A lazy, stack-safe computation that may fail with an error.
40	///
41	/// This is [`Trampoline<Result<A, E>>`] with ergonomic combinators.
42	///
43	/// ### When to Use
44	///
45	/// Use `TryTrampoline` for stack-safe fallible recursion. It provides unlimited recursion
46	/// depth without stack overflow, but requires `'static` types and does not have HKT brands.
47	/// For lightweight fallible deferred computation with HKT support, use
48	/// [`TryThunk`](crate::types::TryThunk). For memoized fallible computation, use
49	/// [`TryLazy`](crate::types::TryLazy).
50	///
51	/// ### Algebraic Properties
52	///
53	/// `TryTrampoline` forms a monad over the success type `A` (with `E` fixed):
54	/// - `TryTrampoline::ok(a).bind(f).evaluate() == f(a).evaluate()` (left identity).
55	/// - `task.bind(TryTrampoline::ok).evaluate() == task.evaluate()` (right identity).
56	/// - `task.bind(f).bind(g).evaluate() == task.bind(|a| f(a).bind(g)).evaluate()` (associativity).
57	///
58	/// On the error channel, `bind` short-circuits: if the computation produces `Err(e)`,
59	/// the continuation `f` is never called and the error propagates directly.
60	///
61	/// ### Limitations
62	///
63	/// **`'static` constraint**: Both `A` and `E` must be `'static` because the
64	/// underlying [`Trampoline`] uses [`Free<ThunkBrand, A>`](crate::types::Free),
65	/// which erases types via [`Box<dyn Any>`] and therefore requires `'static`.
66	/// For fallible deferred computation with borrowed references and lifetime
67	/// polymorphism, use [`TryThunk`](crate::types::TryThunk).
68	///
69	/// **No HKT brand**: `TryTrampoline` does not have a corresponding brand type.
70	/// The `'static` constraint inherited from `Free` makes it impossible to
71	/// implement the `Kind` trait, which requires lifetime polymorphism (`for<'a>`).
72	/// Use [`TryThunk`](crate::types::TryThunk) when HKT integration is needed.
73	///
74	/// **`!Send`**: `TryTrampoline` is not `Send` because the underlying
75	/// [`Thunk`](crate::types::Thunk) stores a `Box<dyn FnOnce()>` without a `Send`
76	/// bound. For thread-safe fallible deferred computation, use
77	/// [`TrySendThunk`](crate::types::TrySendThunk) (not stack-safe) or
78	/// [`ArcTryLazy`](crate::types::ArcTryLazy) (memoized).
79	///
80	/// **Not memoized**: Each call to [`evaluate`](TryTrampoline::evaluate) re-runs
81	/// the entire computation. For memoized fallible computation, wrap in
82	/// [`TryLazy`](crate::types::TryLazy).
83	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
84	///
85	pub struct TryTrampoline<A: 'static, E: 'static>(
86		/// The internal `Trampoline` wrapping a `Result`.
87		Trampoline<Result<A, E>>,
88	);
89
90	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
91	#[document_parameters("The fallible trampoline computation.")]
92	impl<A: 'static, E: 'static> TryTrampoline<A, E> {
93		/// Creates a successful `TryTrampoline`.
94		#[document_signature]
95		///
96		#[document_parameters("The success value.")]
97		///
98		#[document_returns("A `TryTrampoline` representing success.")]
99		///
100		#[document_examples]
101		///
102		/// ```
103		/// use fp_library::types::*;
104		///
105		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
106		/// assert_eq!(task.evaluate(), Ok(42));
107		/// ```
108		#[inline]
109		pub fn ok(a: A) -> Self {
110			TryTrampoline(Trampoline::pure(Ok(a)))
111		}
112
113		/// Creates a successful `TryTrampoline`.
114		///
115		/// This is an alias for [`ok`](TryTrampoline::ok), provided for consistency
116		/// with other types in the library.
117		#[document_signature]
118		///
119		#[document_parameters("The success value.")]
120		///
121		#[document_returns("A `TryTrampoline` representing success.")]
122		///
123		#[document_examples]
124		///
125		/// ```
126		/// use fp_library::types::*;
127		///
128		/// let task: TryTrampoline<i32, String> = TryTrampoline::pure(42);
129		/// assert_eq!(task.evaluate(), Ok(42));
130		/// ```
131		pub fn pure(a: A) -> Self {
132			Self::ok(a)
133		}
134
135		/// Unwraps the newtype to expose the inner `Trampoline<Result<A, E>>`.
136		#[document_signature]
137		///
138		#[document_returns("The inner `Trampoline<Result<A, E>>`.")]
139		///
140		#[document_examples]
141		///
142		/// ```
143		/// use fp_library::types::*;
144		///
145		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
146		/// let inner: Trampoline<Result<i32, String>> = task.into_inner();
147		/// assert_eq!(inner.evaluate(), Ok(42));
148		/// ```
149		pub fn into_inner(self) -> Trampoline<Result<A, E>> {
150			self.0
151		}
152
153		/// Creates a failed `TryTrampoline`.
154		#[document_signature]
155		///
156		#[document_parameters("The error value.")]
157		///
158		#[document_returns("A `TryTrampoline` representing failure.")]
159		///
160		#[document_examples]
161		///
162		/// ```
163		/// use fp_library::types::*;
164		///
165		/// let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
166		/// assert_eq!(task.evaluate(), Err("error".to_string()));
167		/// ```
168		#[inline]
169		pub fn err(e: E) -> Self {
170			TryTrampoline(Trampoline::pure(Err(e)))
171		}
172
173		/// Creates a lazy `TryTrampoline` that may fail.
174		#[document_signature]
175		///
176		#[document_parameters("The closure to execute.")]
177		///
178		#[document_returns("A `TryTrampoline` that executes `f` when run.")]
179		///
180		#[document_examples]
181		///
182		/// ```
183		/// use fp_library::types::*;
184		///
185		/// let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
186		/// assert_eq!(task.evaluate(), Ok(42));
187		/// ```
188		#[inline]
189		pub fn new(f: impl FnOnce() -> Result<A, E> + 'static) -> Self {
190			TryTrampoline(Trampoline::new(f))
191		}
192
193		/// Defers the construction of a `TryTrampoline`.
194		///
195		/// Use this for stack-safe recursion.
196		#[document_signature]
197		///
198		#[document_parameters("A thunk that returns the next step.")]
199		///
200		#[document_returns("A `TryTrampoline` that executes `f` to get the next step.")]
201		///
202		#[document_examples]
203		///
204		/// Stack-safe recursion:
205		///
206		/// ```
207		/// use fp_library::types::*;
208		///
209		/// fn factorial(
210		/// 	n: i32,
211		/// 	acc: i32,
212		/// ) -> TryTrampoline<i32, String> {
213		/// 	if n < 0 {
214		/// 		TryTrampoline::err("Negative input".to_string())
215		/// 	} else if n == 0 {
216		/// 		TryTrampoline::ok(acc)
217		/// 	} else {
218		/// 		TryTrampoline::defer(move || factorial(n - 1, n * acc))
219		/// 	}
220		/// }
221		///
222		/// let task = factorial(5, 1);
223		/// assert_eq!(task.evaluate(), Ok(120));
224		/// ```
225		///
226		/// ```
227		/// use fp_library::types::*;
228		///
229		/// let task: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(42));
230		/// assert_eq!(task.evaluate(), Ok(42));
231		/// ```
232		#[inline]
233		pub fn defer(f: impl FnOnce() -> TryTrampoline<A, E> + 'static) -> Self {
234			TryTrampoline(Trampoline::defer(move || f().0))
235		}
236
237		/// Maps over the success value.
238		#[document_signature]
239		///
240		#[document_type_parameters("The type of the new success value.")]
241		///
242		#[document_parameters("The function to apply to the success value.")]
243		///
244		#[document_returns("A new `TryTrampoline` with the transformed success value.")]
245		///
246		#[document_examples]
247		///
248		/// ```
249		/// use fp_library::types::*;
250		///
251		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
252		/// assert_eq!(task.evaluate(), Ok(20));
253		/// ```
254		#[inline]
255		pub fn map<B: 'static>(
256			self,
257			func: impl FnOnce(A) -> B + 'static,
258		) -> TryTrampoline<B, E> {
259			TryTrampoline(self.0.map(|result| result.map(func)))
260		}
261
262		/// Maps over the error value.
263		#[document_signature]
264		///
265		#[document_type_parameters("The type of the new error value.")]
266		///
267		#[document_parameters("The function to apply to the error value.")]
268		///
269		#[document_returns("A new `TryTrampoline` with the transformed error value.")]
270		///
271		#[document_examples]
272		///
273		/// ```
274		/// use fp_library::types::*;
275		///
276		/// let task: TryTrampoline<i32, String> =
277		/// 	TryTrampoline::err("error".to_string()).map_err(|e| e.to_uppercase());
278		/// assert_eq!(task.evaluate(), Err("ERROR".to_string()));
279		/// ```
280		#[inline]
281		pub fn map_err<E2: 'static>(
282			self,
283			func: impl FnOnce(E) -> E2 + 'static,
284		) -> TryTrampoline<A, E2> {
285			TryTrampoline(self.0.map(|result| result.map_err(func)))
286		}
287
288		/// Maps over both the success and error values simultaneously.
289		#[document_signature]
290		///
291		#[document_type_parameters(
292			"The type of the new success value.",
293			"The type of the new error value."
294		)]
295		///
296		#[document_parameters(
297			"The function to apply to the success value.",
298			"The function to apply to the error value."
299		)]
300		///
301		#[document_returns("A new `TryTrampoline` with both sides transformed.")]
302		///
303		#[document_examples]
304		///
305		/// ```
306		/// use fp_library::types::*;
307		///
308		/// let ok_task: TryTrampoline<i32, String> = TryTrampoline::ok(10);
309		/// let mapped = ok_task.bimap(|x| x * 2, |e| e.len());
310		/// assert_eq!(mapped.evaluate(), Ok(20));
311		///
312		/// let err_task: TryTrampoline<i32, String> = TryTrampoline::err("hello".to_string());
313		/// let mapped = err_task.bimap(|x| x * 2, |e| e.len());
314		/// assert_eq!(mapped.evaluate(), Err(5));
315		/// ```
316		#[inline]
317		pub fn bimap<B: 'static, F: 'static>(
318			self,
319			f: impl FnOnce(A) -> B + 'static,
320			g: impl FnOnce(E) -> F + 'static,
321		) -> TryTrampoline<B, F> {
322			TryTrampoline(self.0.map(|result| match result {
323				Ok(a) => Ok(f(a)),
324				Err(e) => Err(g(e)),
325			}))
326		}
327
328		/// Chains fallible computations.
329		#[document_signature]
330		///
331		#[document_type_parameters("The type of the new success value.")]
332		///
333		#[document_parameters("The function to apply to the success value.")]
334		///
335		#[document_returns("A new `TryTrampoline` that chains `f` after this task.")]
336		///
337		#[document_examples]
338		///
339		/// ```
340		/// use fp_library::types::*;
341		///
342		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
343		/// assert_eq!(task.evaluate(), Ok(20));
344		/// ```
345		#[inline]
346		pub fn bind<B: 'static>(
347			self,
348			f: impl FnOnce(A) -> TryTrampoline<B, E> + 'static,
349		) -> TryTrampoline<B, E> {
350			TryTrampoline(self.0.bind(|result| match result {
351				Ok(a) => f(a).0,
352				Err(e) => Trampoline::pure(Err(e)),
353			}))
354		}
355
356		/// Recovers from an error.
357		#[document_signature]
358		///
359		#[document_parameters("The function to apply to the error value.")]
360		///
361		#[document_returns("A new `TryTrampoline` that attempts to recover from failure.")]
362		///
363		#[document_examples]
364		///
365		/// ```
366		/// use fp_library::types::*;
367		///
368		/// let task: TryTrampoline<i32, String> =
369		/// 	TryTrampoline::err("error".to_string()).catch(|_| TryTrampoline::ok(42));
370		/// assert_eq!(task.evaluate(), Ok(42));
371		/// ```
372		#[inline]
373		pub fn catch(
374			self,
375			f: impl FnOnce(E) -> TryTrampoline<A, E> + 'static,
376		) -> Self {
377			TryTrampoline(self.0.bind(|result| match result {
378				Ok(a) => Trampoline::pure(Ok(a)),
379				Err(e) => f(e).0,
380			}))
381		}
382
383		/// Recovers from an error using a fallible recovery function that may produce a different error type.
384		///
385		/// Unlike [`catch`](TryTrampoline::catch), `catch_with` allows the recovery function to return a
386		/// `TryTrampoline` with a different error type `E2`. On success, the value is passed through
387		/// unchanged. On failure, the recovery function is applied to the error value and its
388		/// resulting `TryTrampoline` is composed via `bind`, preserving stack safety through deeply chained recovery operations.
389		#[document_signature]
390		///
391		#[document_type_parameters("The error type produced by the recovery computation.")]
392		///
393		#[document_parameters("The monadic recovery function applied to the error value.")]
394		///
395		#[document_returns(
396			"A new `TryTrampoline` that either passes through the success value or uses the result of the recovery computation."
397		)]
398		///
399		#[document_examples]
400		///
401		/// ```
402		/// use fp_library::types::*;
403		///
404		/// let recovered: TryTrampoline<i32, i32> =
405		/// 	TryTrampoline::<i32, &str>::err("error").catch_with(|_| TryTrampoline::err(42));
406		/// assert_eq!(recovered.evaluate(), Err(42));
407		///
408		/// let ok: TryTrampoline<i32, i32> =
409		/// 	TryTrampoline::<i32, &str>::ok(1).catch_with(|_| TryTrampoline::err(42));
410		/// assert_eq!(ok.evaluate(), Ok(1));
411		/// ```
412		#[inline]
413		pub fn catch_with<E2: 'static>(
414			self,
415			f: impl FnOnce(E) -> TryTrampoline<A, E2> + 'static,
416		) -> TryTrampoline<A, E2> {
417			TryTrampoline(self.0.bind(move |result| match result {
418				Ok(a) => Trampoline::pure(Ok(a)),
419				Err(e) => f(e).0,
420			}))
421		}
422
423		/// Combines two `TryTrampoline`s, running both and combining results.
424		///
425		/// Short-circuits on error: if `self` fails, `other` is never evaluated.
426		#[document_signature]
427		///
428		#[document_type_parameters(
429			"The type of the second computation's success value.",
430			"The type of the combined result."
431		)]
432		///
433		#[document_parameters("The second computation.", "The function to combine the results.")]
434		///
435		#[document_returns("A new `TryTrampoline` producing the combined result.")]
436		///
437		#[document_examples]
438		///
439		/// ```
440		/// use fp_library::types::*;
441		///
442		/// let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
443		/// let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
444		/// let t3 = t1.lift2(t2, |a, b| a + b);
445		/// assert_eq!(t3.evaluate(), Ok(30));
446		/// ```
447		#[inline]
448		pub fn lift2<B: 'static, C: 'static>(
449			self,
450			other: TryTrampoline<B, E>,
451			f: impl FnOnce(A, B) -> C + 'static,
452		) -> TryTrampoline<C, E> {
453			self.bind(move |a| other.map(move |b| f(a, b)))
454		}
455
456		/// Sequences two `TryTrampoline`s, discarding the first result.
457		///
458		/// Short-circuits on error: if `self` fails, `other` is never evaluated.
459		#[document_signature]
460		///
461		#[document_type_parameters("The type of the second computation's success value.")]
462		///
463		#[document_parameters("The second computation.")]
464		///
465		#[document_returns(
466			"A new `TryTrampoline` that runs both computations and returns the result of the second."
467		)]
468		///
469		#[document_examples]
470		///
471		/// ```
472		/// use fp_library::types::*;
473		///
474		/// let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
475		/// let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
476		/// let t3 = t1.then(t2);
477		/// assert_eq!(t3.evaluate(), Ok(20));
478		/// ```
479		#[inline]
480		pub fn then<B: 'static>(
481			self,
482			other: TryTrampoline<B, E>,
483		) -> TryTrampoline<B, E> {
484			self.bind(move |_| other)
485		}
486
487		/// Stack-safe tail recursion for fallible computations.
488		///
489		/// The step function returns `TryTrampoline<ControlFlow<A, S>, E>`, and the
490		/// loop short-circuits on error.
491		///
492		/// # Clone Bound
493		///
494		/// The function `f` must implement `Clone` because each iteration
495		/// of the recursion may need its own copy. Most closures naturally
496		/// implement `Clone` when all their captures implement `Clone`.
497		///
498		/// For closures that don't implement `Clone`, use `arc_tail_rec_m`
499		/// which wraps the closure in `Arc` internally.
500		#[document_signature]
501		///
502		#[document_type_parameters("The type of the state.")]
503		///
504		#[document_parameters(
505			"The function that performs one step of the recursion.",
506			"The initial state."
507		)]
508		///
509		#[document_returns("A `TryTrampoline` that performs the recursion.")]
510		#[document_examples]
511		///
512		/// ```
513		/// use {
514		/// 	core::ops::ControlFlow,
515		/// 	fp_library::types::TryTrampoline,
516		/// };
517		///
518		/// // Fallible factorial using tail recursion
519		/// fn factorial(n: i32) -> TryTrampoline<i32, String> {
520		/// 	TryTrampoline::tail_rec_m(
521		/// 		|(n, acc)| {
522		/// 			if n < 0 {
523		/// 				TryTrampoline::err("Negative input".to_string())
524		/// 			} else if n <= 1 {
525		/// 				TryTrampoline::ok(ControlFlow::Break(acc))
526		/// 			} else {
527		/// 				TryTrampoline::ok(ControlFlow::Continue((n - 1, n * acc)))
528		/// 			}
529		/// 		},
530		/// 		(n, 1),
531		/// 	)
532		/// }
533		///
534		/// assert_eq!(factorial(5).evaluate(), Ok(120));
535		/// assert_eq!(factorial(-1).evaluate(), Err("Negative input".to_string()));
536		/// ```
537		pub fn tail_rec_m<S: 'static>(
538			f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
539			initial: S,
540		) -> Self {
541			fn go<S: 'static, A: 'static, E: 'static>(
542				f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + Clone + 'static,
543				s: S,
544			) -> Trampoline<Result<A, E>> {
545				Trampoline::defer(move || {
546					let result = f(s);
547					result.0.bind(move |r| match r {
548						Ok(ControlFlow::Continue(next)) => go(f, next),
549						Ok(ControlFlow::Break(a)) => Trampoline::pure(Ok(a)),
550						Err(e) => Trampoline::pure(Err(e)),
551					})
552				})
553			}
554			TryTrampoline(go(f, initial))
555		}
556
557		/// Arc-wrapped version of `tail_rec_m` for non-Clone closures.
558		///
559		/// Use this when your closure captures non-Clone state.
560		#[document_signature]
561		///
562		#[document_type_parameters("The type of the state.")]
563		///
564		#[document_parameters(
565			"The function that performs one step of the recursion.",
566			"The initial state."
567		)]
568		///
569		#[document_returns("A `TryTrampoline` that performs the recursion.")]
570		#[document_examples]
571		///
572		/// ```
573		/// use {
574		/// 	core::ops::ControlFlow,
575		/// 	fp_library::types::TryTrampoline,
576		/// 	std::sync::{
577		/// 		Arc,
578		/// 		atomic::{
579		/// 			AtomicUsize,
580		/// 			Ordering,
581		/// 		},
582		/// 	},
583		/// };
584		///
585		/// // Closure captures non-Clone state
586		/// let counter = Arc::new(AtomicUsize::new(0));
587		/// let counter_clone = Arc::clone(&counter);
588		/// let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
589		/// 	move |n| {
590		/// 		counter_clone.fetch_add(1, Ordering::SeqCst);
591		/// 		if n == 0 {
592		/// 			TryTrampoline::ok(ControlFlow::Break(0))
593		/// 		} else {
594		/// 			TryTrampoline::ok(ControlFlow::Continue(n - 1))
595		/// 		}
596		/// 	},
597		/// 	100,
598		/// );
599		/// assert_eq!(task.evaluate(), Ok(0));
600		/// assert_eq!(counter.load(Ordering::SeqCst), 101);
601		/// ```
602		pub fn arc_tail_rec_m<S: 'static>(
603			f: impl Fn(S) -> TryTrampoline<ControlFlow<A, S>, E> + 'static,
604			initial: S,
605		) -> Self {
606			use std::sync::Arc;
607			let f = Arc::new(f);
608			let wrapper = move |s: S| {
609				let f = Arc::clone(&f);
610				f(s)
611			};
612			Self::tail_rec_m(wrapper, initial)
613		}
614
615		/// Runs the computation, returning the result.
616		#[document_signature]
617		///
618		#[document_returns("The result of the computation.")]
619		///
620		#[document_examples]
621		///
622		/// ```
623		/// use fp_library::types::*;
624		///
625		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
626		/// assert_eq!(task.evaluate(), Ok(42));
627		/// ```
628		#[inline]
629		pub fn evaluate(self) -> Result<A, E> {
630			self.0.evaluate()
631		}
632
633		/// Combines two `TryTrampoline` values using the inner type's [`Semigroup`].
634		///
635		/// Both computations are evaluated. If both succeed, their results are
636		/// combined via [`Semigroup::append`]. If either fails, the first error
637		/// is propagated (short-circuiting on the left).
638		#[document_signature]
639		///
640		#[document_parameters(
641			"The second `TryTrampoline` whose result will be combined with this one."
642		)]
643		///
644		#[document_returns("A new `TryTrampoline` producing the combined result.")]
645		///
646		#[document_examples]
647		///
648		/// ```
649		/// use fp_library::types::*;
650		///
651		/// let t1: TryTrampoline<Vec<i32>, String> = TryTrampoline::ok(vec![1, 2]);
652		/// let t2: TryTrampoline<Vec<i32>, String> = TryTrampoline::ok(vec![3, 4]);
653		/// assert_eq!(t1.append(t2).evaluate(), Ok(vec![1, 2, 3, 4]));
654		/// ```
655		#[inline]
656		pub fn append(
657			self,
658			other: TryTrampoline<A, E>,
659		) -> TryTrampoline<A, E>
660		where
661			A: Semigroup + 'static, {
662			self.lift2(other, Semigroup::append)
663		}
664
665		/// Creates a `TryTrampoline` that produces the identity element for the given [`Monoid`].
666		#[document_signature]
667		///
668		#[document_returns(
669			"A `TryTrampoline` producing the monoid identity element wrapped in `Ok`."
670		)]
671		///
672		#[document_examples]
673		///
674		/// ```
675		/// use fp_library::types::*;
676		///
677		/// let t: TryTrampoline<Vec<i32>, String> = TryTrampoline::empty();
678		/// assert_eq!(t.evaluate(), Ok(Vec::<i32>::new()));
679		/// ```
680		#[inline]
681		pub fn empty() -> TryTrampoline<A, E>
682		where
683			A: Monoid + 'static, {
684			TryTrampoline::ok(Monoid::empty())
685		}
686
687		/// Converts this `TryTrampoline` into a memoized [`RcTryLazy`](crate::types::RcTryLazy) value.
688		///
689		/// The computation will be evaluated at most once; subsequent accesses
690		/// return the cached result.
691		#[document_signature]
692		///
693		#[document_returns(
694			"A memoized `RcTryLazy` value that evaluates this trampoline on first access."
695		)]
696		///
697		#[document_examples]
698		///
699		/// ```
700		/// use fp_library::types::*;
701		///
702		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
703		/// let lazy = task.into_rc_try_lazy();
704		/// assert_eq!(lazy.evaluate(), Ok(&42));
705		/// ```
706		#[inline]
707		pub fn into_rc_try_lazy(self) -> crate::types::RcTryLazy<'static, A, E> {
708			crate::types::RcTryLazy::from(self)
709		}
710
711		/// Evaluates this `TryTrampoline` and wraps the result in a thread-safe [`ArcTryLazy`](crate::types::ArcTryLazy).
712		///
713		/// The trampoline is evaluated eagerly because its inner closures are
714		/// not `Send`. The result is stored in an `ArcTryLazy` for thread-safe sharing.
715		#[document_signature]
716		///
717		#[document_returns("A thread-safe `ArcTryLazy` containing the eagerly evaluated result.")]
718		///
719		#[document_examples]
720		///
721		/// ```
722		/// use fp_library::types::*;
723		///
724		/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
725		/// let lazy = task.into_arc_try_lazy();
726		/// assert_eq!(lazy.evaluate(), Ok(&42));
727		/// ```
728		#[inline]
729		pub fn into_arc_try_lazy(self) -> crate::types::ArcTryLazy<'static, A, E>
730		where
731			A: Send + Sync,
732			E: Send + Sync, {
733			crate::types::ArcTryLazy::from(self)
734		}
735
736		/// Peels off one layer of the trampoline.
737		///
738		/// Returns `Ok(result)` if the computation has completed (where `result` is
739		/// itself a `Result<A, E>` indicating success or failure), or
740		/// `Err(thunk)` if the computation is suspended. Evaluating the returned
741		/// [`Thunk`] yields the next `TryTrampoline` step.
742		///
743		/// This is useful for implementing custom interpreters or drivers that need
744		/// to interleave trampoline steps with other logic (e.g., logging, resource
745		/// cleanup, cooperative scheduling).
746		#[document_signature]
747		///
748		#[document_returns(
749			"`Ok(result)` if the computation is finished, `Err(thunk)` if it is suspended."
750		)]
751		///
752		#[document_examples]
753		///
754		/// ```
755		/// use fp_library::types::*;
756		///
757		/// // A completed TryTrampoline resumes immediately.
758		/// let t: TryTrampoline<i32, String> = TryTrampoline::ok(42);
759		/// assert_eq!(t.resume().unwrap(), Ok(42));
760		///
761		/// // A deferred TryTrampoline is suspended.
762		/// let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(99));
763		/// match t.resume() {
764		/// 	Ok(_) => panic!("expected suspension"),
765		/// 	Err(thunk) => {
766		/// 		let next = thunk.evaluate();
767		/// 		assert_eq!(next.resume().unwrap(), Ok(99));
768		/// 	}
769		/// }
770		/// ```
771		pub fn resume(self) -> Result<Result<A, E>, Thunk<'static, TryTrampoline<A, E>>> {
772			match self.0.resume() {
773				Ok(result) => Ok(result),
774				Err(thunk) => Err(thunk.map(TryTrampoline)),
775			}
776		}
777	}
778
779	#[document_type_parameters("The type of the computed value.", "The type of the error value.")]
780	impl<A: 'static, E: 'static> TryTrampoline<A, E> {
781		/// Creates a `TryTrampoline` that catches unwinds (panics), converting the
782		/// panic payload using a custom conversion function.
783		///
784		/// The closure `f` is executed when the trampoline is evaluated. If `f`
785		/// panics, the panic payload is passed to `handler` to produce the
786		/// error value. If `f` returns normally, the value is wrapped in `Ok`.
787		#[document_signature]
788		///
789		#[document_parameters(
790			"The closure that might panic.",
791			"The function that converts a panic payload into the error type."
792		)]
793		///
794		#[document_returns(
795			"A new `TryTrampoline` instance where panics are converted to `Err(E)` via the handler."
796		)]
797		///
798		#[document_examples]
799		///
800		/// ```
801		/// use fp_library::types::*;
802		///
803		/// let task = TryTrampoline::<i32, i32>::catch_unwind_with(
804		/// 	|| {
805		/// 		if true {
806		/// 			panic!("oops")
807		/// 		}
808		/// 		42
809		/// 	},
810		/// 	|_payload| -1,
811		/// );
812		/// assert_eq!(task.evaluate(), Err(-1));
813		/// ```
814		pub fn catch_unwind_with(
815			f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static,
816			handler: impl FnOnce(Box<dyn std::any::Any + Send>) -> E + 'static,
817		) -> Self {
818			Self::new(move || std::panic::catch_unwind(f).map_err(handler))
819		}
820	}
821
822	#[document_type_parameters("The type of the computed value.")]
823	impl<A: 'static> TryTrampoline<A, String> {
824		/// Creates a `TryTrampoline` that catches unwinds (panics).
825		///
826		/// The closure is executed when the trampoline is evaluated. If the closure
827		/// panics, the panic payload is converted to a `String` error. If the
828		/// closure returns normally, the value is wrapped in `Ok`.
829		///
830		/// This is a convenience wrapper around [`catch_unwind_with`](TryTrampoline::catch_unwind_with)
831		/// that uses the default panic payload to string conversion.
832		#[document_signature]
833		///
834		#[document_parameters("The closure that might panic.")]
835		///
836		#[document_returns(
837			"A new `TryTrampoline` instance where panics are converted to `Err(String)`."
838		)]
839		///
840		#[document_examples]
841		///
842		/// ```
843		/// use fp_library::types::*;
844		///
845		/// let task = TryTrampoline::<i32, String>::catch_unwind(|| {
846		/// 	if true {
847		/// 		panic!("oops")
848		/// 	}
849		/// 	42
850		/// });
851		/// assert_eq!(task.evaluate(), Err("oops".to_string()));
852		/// ```
853		pub fn catch_unwind(f: impl FnOnce() -> A + std::panic::UnwindSafe + 'static) -> Self {
854			Self::catch_unwind_with(f, crate::utils::panic_payload_to_string)
855		}
856	}
857
858	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
859	impl<A, E> From<Trampoline<A>> for TryTrampoline<A, E>
860	where
861		A: 'static,
862		E: 'static,
863	{
864		#[document_signature]
865		#[document_parameters("The trampoline computation to convert.")]
866		#[document_returns("A new `TryTrampoline` instance that wraps the trampoline.")]
867		#[document_examples]
868		///
869		/// ```
870		/// use fp_library::types::*;
871		/// let task = Trampoline::pure(42);
872		/// let try_task: TryTrampoline<i32, ()> = TryTrampoline::from(task);
873		/// assert_eq!(try_task.evaluate(), Ok(42));
874		/// ```
875		fn from(task: Trampoline<A>) -> Self {
876			TryTrampoline(task.map(Ok))
877		}
878	}
879
880	#[document_type_parameters(
881		"The type of the success value.",
882		"The type of the error value.",
883		"The memoization configuration."
884	)]
885	impl<A, E, Config> From<Lazy<'static, A, Config>> for TryTrampoline<A, E>
886	where
887		A: Clone + 'static,
888		E: 'static,
889		Config: LazyConfig,
890	{
891		/// Converts a [`Lazy`] value into a [`TryTrampoline`] that defers evaluation.
892		///
893		/// The `Lazy` is not forced at conversion time; instead, the `TryTrampoline`
894		/// defers evaluation until it is run. This is the same deferred semantics as
895		/// [`From<TryLazy>`](#impl-From<TryLazy<'static,+A,+E,+Config>>-for-TryTrampoline<A,+E>).
896		#[document_signature]
897		#[document_parameters("The lazy value to convert.")]
898		#[document_returns("A new `TryTrampoline` instance that wraps the lazy value.")]
899		#[document_examples]
900		///
901		/// ```
902		/// use fp_library::types::*;
903		/// let lazy = Lazy::<_, RcLazyConfig>::pure(42);
904		/// let try_task: TryTrampoline<i32, ()> = TryTrampoline::from(lazy);
905		/// assert_eq!(try_task.evaluate(), Ok(42));
906		/// ```
907		fn from(memo: Lazy<'static, A, Config>) -> Self {
908			TryTrampoline(Trampoline::new(move || Ok(memo.evaluate().clone())))
909		}
910	}
911
912	#[document_type_parameters(
913		"The type of the success value.",
914		"The type of the error value.",
915		"The memoization configuration."
916	)]
917	impl<A, E, Config> From<TryLazy<'static, A, E, Config>> for TryTrampoline<A, E>
918	where
919		A: Clone + 'static,
920		E: Clone + 'static,
921		Config: TryLazyConfig,
922	{
923		/// Converts a [`TryLazy`] value into a [`TryTrampoline`] that defers evaluation.
924		///
925		/// This conversion defers forcing the `TryLazy` until the `TryTrampoline` is run.
926		/// The cost depends on the [`Clone`] implementations of `A` and `E`.
927		#[document_signature]
928		#[document_parameters("The fallible lazy value to convert.")]
929		#[document_returns("A new `TryTrampoline` instance that wraps the fallible lazy value.")]
930		#[document_examples]
931		///
932		/// ```
933		/// use fp_library::types::*;
934		/// let lazy = TryLazy::<_, _, RcLazyConfig>::new(|| Ok::<i32, ()>(42));
935		/// let try_task = TryTrampoline::from(lazy);
936		/// assert_eq!(try_task.evaluate(), Ok(42));
937		/// ```
938		fn from(memo: TryLazy<'static, A, E, Config>) -> Self {
939			TryTrampoline(Trampoline::new(move || {
940				let result = memo.evaluate();
941				match result {
942					Ok(a) => Ok(a.clone()),
943					Err(e) => Err(e.clone()),
944				}
945			}))
946		}
947	}
948
949	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
950	impl<A, E> From<crate::types::TryThunk<'static, A, E>> for TryTrampoline<A, E>
951	where
952		A: 'static,
953		E: 'static,
954	{
955		/// Converts a `'static` [`TryThunk`](crate::types::TryThunk) into a `TryTrampoline`.
956		///
957		/// This lifts a non-stack-safe `TryThunk` into the stack-safe `TryTrampoline`
958		/// execution model. The resulting `TryTrampoline` evaluates the thunk when run.
959		#[document_signature]
960		#[document_parameters("The fallible thunk to convert.")]
961		#[document_returns("A new `TryTrampoline` instance that evaluates the thunk.")]
962		#[document_examples]
963		///
964		/// ```
965		/// use fp_library::types::*;
966		/// let thunk = TryThunk::new(|| Ok::<i32, String>(42));
967		/// let task = TryTrampoline::from(thunk);
968		/// assert_eq!(task.evaluate(), Ok(42));
969		/// ```
970		fn from(thunk: crate::types::TryThunk<'static, A, E>) -> Self {
971			TryTrampoline::new(move || thunk.evaluate())
972		}
973	}
974
975	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
976	impl<A, E> From<Result<A, E>> for TryTrampoline<A, E>
977	where
978		A: 'static,
979		E: 'static,
980	{
981		#[document_signature]
982		#[document_parameters("The result to convert.")]
983		#[document_returns("A new `TryTrampoline` instance that produces the result.")]
984		#[document_examples]
985		///
986		/// ```
987		/// use fp_library::types::*;
988		/// let ok_task: TryTrampoline<i32, String> = TryTrampoline::from(Ok(42));
989		/// assert_eq!(ok_task.evaluate(), Ok(42));
990		///
991		/// let err_task: TryTrampoline<i32, String> = TryTrampoline::from(Err("error".to_string()));
992		/// assert_eq!(err_task.evaluate(), Err("error".to_string()));
993		/// ```
994		fn from(result: Result<A, E>) -> Self {
995			TryTrampoline(Trampoline::pure(result))
996		}
997	}
998
999	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
1000	impl<A, E> Deferrable<'static> for TryTrampoline<A, E>
1001	where
1002		A: 'static,
1003		E: 'static,
1004	{
1005		/// Creates a value from a computation that produces the value.
1006		#[document_signature]
1007		///
1008		#[document_parameters("A thunk that produces the value.")]
1009		///
1010		#[document_returns("The deferred value.")]
1011		///
1012		#[document_examples]
1013		///
1014		/// ```
1015		/// use fp_library::{
1016		/// 	brands::*,
1017		/// 	classes::Deferrable,
1018		/// 	functions::*,
1019		/// 	types::*,
1020		/// };
1021		///
1022		/// let task: TryTrampoline<i32, String> = Deferrable::defer(|| TryTrampoline::ok(42));
1023		/// assert_eq!(task.evaluate(), Ok(42));
1024		/// ```
1025		fn defer(f: impl FnOnce() -> Self + 'static) -> Self
1026		where
1027			Self: Sized, {
1028			TryTrampoline(Trampoline::defer(move || f().0))
1029		}
1030	}
1031
1032	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
1033	impl<A, E> Semigroup for TryTrampoline<A, E>
1034	where
1035		A: Semigroup + 'static,
1036		E: 'static,
1037	{
1038		/// Combines two `TryTrampoline` computations.
1039		///
1040		/// Both computations are evaluated; if both succeed, their results are combined
1041		/// using the inner `Semigroup`. If either fails, the first error is propagated.
1042		#[document_signature]
1043		///
1044		#[document_parameters(
1045			"The first `TryTrampoline` computation.",
1046			"The second `TryTrampoline` computation."
1047		)]
1048		///
1049		#[document_returns(
1050			"A `TryTrampoline` that evaluates both and combines the results, or propagates the first error."
1051		)]
1052		///
1053		#[document_examples]
1054		///
1055		/// ```
1056		/// use fp_library::{
1057		/// 	classes::Semigroup,
1058		/// 	types::*,
1059		/// };
1060		///
1061		/// let a: TryTrampoline<String, ()> = TryTrampoline::ok("hello".to_string());
1062		/// let b: TryTrampoline<String, ()> = TryTrampoline::ok(" world".to_string());
1063		/// let combined = Semigroup::append(a, b);
1064		/// assert_eq!(combined.evaluate(), Ok("hello world".to_string()));
1065		/// ```
1066		fn append(
1067			a: Self,
1068			b: Self,
1069		) -> Self {
1070			a.lift2(b, Semigroup::append)
1071		}
1072	}
1073
1074	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
1075	impl<A, E> Monoid for TryTrampoline<A, E>
1076	where
1077		A: Monoid + 'static,
1078		E: 'static,
1079	{
1080		/// Returns a `TryTrampoline` containing the monoidal identity.
1081		#[document_signature]
1082		///
1083		#[document_returns("A `TryTrampoline` that succeeds with the monoidal identity element.")]
1084		///
1085		#[document_examples]
1086		///
1087		/// ```
1088		/// use fp_library::{
1089		/// 	classes::Monoid,
1090		/// 	types::*,
1091		/// };
1092		///
1093		/// let e: TryTrampoline<String, ()> = Monoid::empty();
1094		/// assert_eq!(e.evaluate(), Ok(String::new()));
1095		/// ```
1096		fn empty() -> Self {
1097			TryTrampoline::ok(A::empty())
1098		}
1099	}
1100
1101	#[document_type_parameters("The type of the success value.", "The type of the error value.")]
1102	#[document_parameters("The try-trampoline to format.")]
1103	impl<A: 'static, E: 'static> fmt::Debug for TryTrampoline<A, E> {
1104		/// Formats the try-trampoline without evaluating it.
1105		#[document_signature]
1106		#[document_parameters("The formatter.")]
1107		#[document_returns("The formatting result.")]
1108		#[document_examples]
1109		///
1110		/// ```
1111		/// use fp_library::types::*;
1112		/// let task = TryTrampoline::<i32, ()>::ok(42);
1113		/// assert_eq!(format!("{:?}", task), "TryTrampoline(<unevaluated>)");
1114		/// ```
1115		fn fmt(
1116			&self,
1117			f: &mut fmt::Formatter<'_>,
1118		) -> fmt::Result {
1119			f.write_str("TryTrampoline(<unevaluated>)")
1120		}
1121	}
1122}
1123pub use inner::*;
1124
1125#[cfg(test)]
1126mod tests {
1127	use {
1128		super::*,
1129		crate::types::Trampoline,
1130		core::ops::ControlFlow,
1131		quickcheck_macros::quickcheck,
1132	};
1133
1134	/// Tests `TryTrampoline::ok`.
1135	///
1136	/// Verifies that `ok` creates a successful task.
1137	#[test]
1138	fn test_try_task_ok() {
1139		let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1140		assert_eq!(task.evaluate(), Ok(42));
1141	}
1142
1143	/// Tests `TryTrampoline::err`.
1144	///
1145	/// Verifies that `err` creates a failed task.
1146	#[test]
1147	fn test_try_task_err() {
1148		let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
1149		assert_eq!(task.evaluate(), Err("error".to_string()));
1150	}
1151
1152	/// Tests `TryTrampoline::map`.
1153	///
1154	/// Verifies that `map` transforms the success value.
1155	#[test]
1156	fn test_try_task_map() {
1157		let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
1158		assert_eq!(task.evaluate(), Ok(20));
1159	}
1160
1161	/// Tests `TryTrampoline::map_err`.
1162	///
1163	/// Verifies that `map_err` transforms the error value.
1164	#[test]
1165	fn test_try_task_map_err() {
1166		let task: TryTrampoline<i32, String> =
1167			TryTrampoline::err("error".to_string()).map_err(|e| e.to_uppercase());
1168		assert_eq!(task.evaluate(), Err("ERROR".to_string()));
1169	}
1170
1171	/// Tests `TryTrampoline::bind`.
1172	///
1173	/// Verifies that `bind` chains computations.
1174	#[test]
1175	fn test_try_task_bind() {
1176		let task: TryTrampoline<i32, String> =
1177			TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
1178		assert_eq!(task.evaluate(), Ok(20));
1179	}
1180
1181	/// Tests `TryTrampoline::or_else`.
1182	///
1183	/// Verifies that `or_else` recovers from failure.
1184	#[test]
1185	fn test_try_task_or_else() {
1186		let task: TryTrampoline<i32, String> =
1187			TryTrampoline::err("error".to_string()).catch(|_| TryTrampoline::ok(42));
1188		assert_eq!(task.evaluate(), Ok(42));
1189	}
1190
1191	/// Tests `TryTrampoline::catch_with`.
1192	///
1193	/// Verifies that `catch_with` recovers from failure using a different error type.
1194	#[test]
1195	fn test_catch_with_recovers() {
1196		let task: TryTrampoline<i32, i32> = TryTrampoline::<i32, String>::err("error".to_string())
1197			.catch_with(|_| TryTrampoline::err(42));
1198		assert_eq!(task.evaluate(), Err(42));
1199	}
1200
1201	/// Tests `TryTrampoline::catch_with` when the computation succeeds.
1202	///
1203	/// Verifies that success values pass through unchanged.
1204	#[test]
1205	fn test_catch_with_success_passes_through() {
1206		let task: TryTrampoline<i32, i32> =
1207			TryTrampoline::<i32, String>::ok(1).catch_with(|_| TryTrampoline::err(42));
1208		assert_eq!(task.evaluate(), Ok(1));
1209	}
1210
1211	/// Tests `TryTrampoline::new`.
1212	///
1213	/// Verifies that `new` creates a lazy task.
1214	#[test]
1215	fn test_try_task_new() {
1216		let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
1217		assert_eq!(task.evaluate(), Ok(42));
1218	}
1219
1220	/// Tests `TryTrampoline::pure`.
1221	///
1222	/// Verifies that `pure` creates a successful task.
1223	#[test]
1224	fn test_try_trampoline_pure() {
1225		let task: TryTrampoline<i32, String> = TryTrampoline::pure(42);
1226		assert_eq!(task.evaluate(), Ok(42));
1227	}
1228
1229	/// Tests `TryTrampoline::into_inner`.
1230	///
1231	/// Verifies that `into_inner` unwraps the newtype.
1232	#[test]
1233	fn test_try_trampoline_into_inner() {
1234		let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1235		let inner: Trampoline<Result<i32, String>> = task.into_inner();
1236		assert_eq!(inner.evaluate(), Ok(42));
1237	}
1238
1239	/// Tests `From<Trampoline>`.
1240	#[test]
1241	fn test_try_task_from_task() {
1242		let task = Trampoline::pure(42);
1243		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(task);
1244		assert_eq!(try_task.evaluate(), Ok(42));
1245	}
1246
1247	/// Tests `From<Lazy>`.
1248	#[test]
1249	fn test_try_task_from_memo() {
1250		use crate::types::ArcLazy;
1251		let memo = ArcLazy::new(|| 42);
1252		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1253		assert_eq!(try_task.evaluate(), Ok(42));
1254	}
1255
1256	/// Tests `From<TryLazy>`.
1257	#[test]
1258	fn test_try_task_from_try_memo() {
1259		use crate::types::ArcTryLazy;
1260		let memo = ArcTryLazy::new(|| Ok(42));
1261		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
1262		assert_eq!(try_task.evaluate(), Ok(42));
1263	}
1264
1265	/// Tests `TryTrampoline::lift2` with two successful values.
1266	///
1267	/// Verifies that `lift2` combines results from both computations.
1268	#[test]
1269	fn test_try_task_lift2_success() {
1270		let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1271		let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1272		let t3 = t1.lift2(t2, |a, b| a + b);
1273		assert_eq!(t3.evaluate(), Ok(30));
1274	}
1275
1276	/// Tests `TryTrampoline::lift2` short-circuits on first error.
1277	///
1278	/// Verifies that if the first computation fails, the second is not evaluated.
1279	#[test]
1280	fn test_try_task_lift2_first_error() {
1281		let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1282		let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1283		let t3 = t1.lift2(t2, |a, b| a + b);
1284		assert_eq!(t3.evaluate(), Err("first".to_string()));
1285	}
1286
1287	/// Tests `TryTrampoline::lift2` propagates second error.
1288	///
1289	/// Verifies that if the second computation fails, the error is propagated.
1290	#[test]
1291	fn test_try_task_lift2_second_error() {
1292		let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1293		let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1294		let t3 = t1.lift2(t2, |a, b| a + b);
1295		assert_eq!(t3.evaluate(), Err("second".to_string()));
1296	}
1297
1298	/// Tests `TryTrampoline::then` with two successful values.
1299	///
1300	/// Verifies that `then` discards the first result and returns the second.
1301	#[test]
1302	fn test_try_task_then_success() {
1303		let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1304		let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1305		let t3 = t1.then(t2);
1306		assert_eq!(t3.evaluate(), Ok(20));
1307	}
1308
1309	/// Tests `TryTrampoline::then` short-circuits on first error.
1310	///
1311	/// Verifies that if the first computation fails, the second is not evaluated.
1312	#[test]
1313	fn test_try_task_then_first_error() {
1314		let t1: TryTrampoline<i32, String> = TryTrampoline::err("first".to_string());
1315		let t2: TryTrampoline<i32, String> = TryTrampoline::ok(20);
1316		let t3 = t1.then(t2);
1317		assert_eq!(t3.evaluate(), Err("first".to_string()));
1318	}
1319
1320	/// Tests `TryTrampoline::then` propagates second error.
1321	///
1322	/// Verifies that if the second computation fails, the error is propagated.
1323	#[test]
1324	fn test_try_task_then_second_error() {
1325		let t1: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1326		let t2: TryTrampoline<i32, String> = TryTrampoline::err("second".to_string());
1327		let t3 = t1.then(t2);
1328		assert_eq!(t3.evaluate(), Err("second".to_string()));
1329	}
1330
1331	/// Tests `TryTrampoline::tail_rec_m` with a factorial computation.
1332	///
1333	/// Verifies both the success and error paths.
1334	#[test]
1335	fn test_try_task_tail_rec_m() {
1336		fn factorial(n: i32) -> TryTrampoline<i32, String> {
1337			TryTrampoline::tail_rec_m(
1338				|(n, acc)| {
1339					if n < 0 {
1340						TryTrampoline::err("Negative input".to_string())
1341					} else if n <= 1 {
1342						TryTrampoline::ok(ControlFlow::Break(acc))
1343					} else {
1344						TryTrampoline::ok(ControlFlow::Continue((n - 1, n * acc)))
1345					}
1346				},
1347				(n, 1),
1348			)
1349		}
1350
1351		assert_eq!(factorial(5).evaluate(), Ok(120));
1352		assert_eq!(factorial(0).evaluate(), Ok(1));
1353		assert_eq!(factorial(1).evaluate(), Ok(1));
1354	}
1355
1356	/// Tests `TryTrampoline::tail_rec_m` error short-circuit.
1357	///
1358	/// Verifies that an error terminates the recursion immediately.
1359	#[test]
1360	fn test_try_task_tail_rec_m_error() {
1361		let task: TryTrampoline<i32, String> = TryTrampoline::tail_rec_m(
1362			|n: i32| {
1363				if n >= 5 {
1364					TryTrampoline::err(format!("too large: {}", n))
1365				} else {
1366					TryTrampoline::ok(ControlFlow::Continue(n + 1))
1367				}
1368			},
1369			0,
1370		);
1371
1372		assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1373	}
1374
1375	/// Tests `TryTrampoline::tail_rec_m` stack safety.
1376	///
1377	/// Verifies that `tail_rec_m` does not overflow the stack with 100,000+ iterations.
1378	#[test]
1379	fn test_try_task_tail_rec_m_stack_safety() {
1380		let n = 100_000u64;
1381		let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1382			|(remaining, acc)| {
1383				if remaining == 0 {
1384					TryTrampoline::ok(ControlFlow::Break(acc))
1385				} else {
1386					TryTrampoline::ok(ControlFlow::Continue((remaining - 1, acc + remaining)))
1387				}
1388			},
1389			(n, 0u64),
1390		);
1391
1392		assert_eq!(task.evaluate(), Ok(n * (n + 1) / 2));
1393	}
1394
1395	/// Tests `TryTrampoline::tail_rec_m` stack safety with error at the end.
1396	///
1397	/// Verifies that error short-circuit works after many successful iterations.
1398	#[test]
1399	fn test_try_task_tail_rec_m_stack_safety_error() {
1400		let n = 100_000u64;
1401		let task: TryTrampoline<u64, String> = TryTrampoline::tail_rec_m(
1402			|remaining| {
1403				if remaining == 0 {
1404					TryTrampoline::err("done iterating".to_string())
1405				} else {
1406					TryTrampoline::ok(ControlFlow::Continue(remaining - 1))
1407				}
1408			},
1409			n,
1410		);
1411
1412		assert_eq!(task.evaluate(), Err("done iterating".to_string()));
1413	}
1414
1415	/// Tests `TryTrampoline::arc_tail_rec_m` with non-Clone closures.
1416	///
1417	/// Verifies that `arc_tail_rec_m` works with closures capturing `Arc<AtomicUsize>`.
1418	#[test]
1419	fn test_try_task_arc_tail_rec_m() {
1420		use std::sync::{
1421			Arc,
1422			atomic::{
1423				AtomicUsize,
1424				Ordering,
1425			},
1426		};
1427
1428		let counter = Arc::new(AtomicUsize::new(0));
1429		let counter_clone = Arc::clone(&counter);
1430
1431		let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1432			move |n| {
1433				counter_clone.fetch_add(1, Ordering::SeqCst);
1434				if n == 0 {
1435					TryTrampoline::ok(ControlFlow::Break(0))
1436				} else {
1437					TryTrampoline::ok(ControlFlow::Continue(n - 1))
1438				}
1439			},
1440			10,
1441		);
1442
1443		assert_eq!(task.evaluate(), Ok(0));
1444		assert_eq!(counter.load(Ordering::SeqCst), 11);
1445	}
1446
1447	/// Tests `TryTrampoline::arc_tail_rec_m` error short-circuit.
1448	///
1449	/// Verifies that `arc_tail_rec_m` correctly propagates errors.
1450	#[test]
1451	fn test_try_task_arc_tail_rec_m_error() {
1452		use std::sync::{
1453			Arc,
1454			atomic::{
1455				AtomicUsize,
1456				Ordering,
1457			},
1458		};
1459
1460		let counter = Arc::new(AtomicUsize::new(0));
1461		let counter_clone = Arc::clone(&counter);
1462
1463		let task: TryTrampoline<i32, String> = TryTrampoline::arc_tail_rec_m(
1464			move |n| {
1465				counter_clone.fetch_add(1, Ordering::SeqCst);
1466				if n >= 5 {
1467					TryTrampoline::err(format!("too large: {}", n))
1468				} else {
1469					TryTrampoline::ok(ControlFlow::Continue(n + 1))
1470				}
1471			},
1472			0,
1473		);
1474
1475		assert_eq!(task.evaluate(), Err("too large: 5".to_string()));
1476		// Should have been called 6 times (0, 1, 2, 3, 4, 5)
1477		assert_eq!(counter.load(Ordering::SeqCst), 6);
1478	}
1479
1480	/// Tests `From<TryThunk>` with a successful thunk.
1481	#[test]
1482	fn test_try_task_from_try_thunk_ok() {
1483		use crate::types::TryThunk;
1484		let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1485		let task = TryTrampoline::from(thunk);
1486		assert_eq!(task.evaluate(), Ok(42));
1487	}
1488
1489	/// Tests `From<TryThunk>` with a failed thunk.
1490	#[test]
1491	fn test_try_task_from_try_thunk_err() {
1492		use crate::types::TryThunk;
1493		let thunk = TryThunk::new(|| Err::<i32, String>("error".to_string()));
1494		let task = TryTrampoline::from(thunk);
1495		assert_eq!(task.evaluate(), Err("error".to_string()));
1496	}
1497
1498	/// Tests bidirectional conversion between `TryThunk` and `TryTrampoline`.
1499	///
1500	/// Verifies that a value survives a round-trip through both conversions.
1501	#[test]
1502	fn test_try_thunk_try_trampoline_round_trip() {
1503		use crate::types::TryThunk;
1504
1505		// TryThunk -> TryTrampoline -> TryThunk (Ok case)
1506		let thunk = TryThunk::new(|| Ok::<i32, String>(42));
1507		let tramp = TryTrampoline::from(thunk);
1508		let thunk_back: TryThunk<i32, String> = TryThunk::from(tramp);
1509		assert_eq!(thunk_back.evaluate(), Ok(42));
1510
1511		// TryTrampoline -> TryThunk -> TryTrampoline (Err case)
1512		let tramp = TryTrampoline::err("fail".to_string());
1513		let thunk: TryThunk<i32, String> = TryThunk::from(tramp);
1514		let tramp_back = TryTrampoline::from(thunk);
1515		assert_eq!(tramp_back.evaluate(), Err("fail".to_string()));
1516	}
1517
1518	/// Tests `From<Result>` with `Ok`.
1519	#[test]
1520	fn test_try_task_from_result_ok() {
1521		let task: TryTrampoline<i32, String> = TryTrampoline::from(Ok(42));
1522		assert_eq!(task.evaluate(), Ok(42));
1523	}
1524
1525	/// Tests `From<Result>` with `Err`.
1526	#[test]
1527	fn test_try_task_from_result_err() {
1528		let task: TryTrampoline<i32, String> = TryTrampoline::from(Err("error".to_string()));
1529		assert_eq!(task.evaluate(), Err("error".to_string()));
1530	}
1531
1532	// Tests for !Send types (Rc)
1533
1534	/// Tests that `TryTrampoline` works with `Rc<T>`, a `!Send` type.
1535	///
1536	/// This verifies that the `Send` bound relaxation allows single-threaded
1537	/// stack-safe fallible recursion with reference-counted types.
1538	#[test]
1539	fn test_try_trampoline_with_rc() {
1540		use std::rc::Rc;
1541
1542		let task: TryTrampoline<Rc<i32>, Rc<String>> = TryTrampoline::ok(Rc::new(42));
1543		assert_eq!(*task.evaluate().unwrap(), 42);
1544	}
1545
1546	/// Tests `TryTrampoline::bind` with `Rc<T>`.
1547	///
1548	/// Verifies that `bind` works correctly when value and error types are `!Send`.
1549	#[test]
1550	fn test_try_trampoline_bind_with_rc() {
1551		use std::rc::Rc;
1552
1553		let task: TryTrampoline<Rc<i32>, Rc<String>> =
1554			TryTrampoline::ok(Rc::new(10)).bind(|rc| TryTrampoline::ok(Rc::new(*rc * 2)));
1555		assert_eq!(*task.evaluate().unwrap(), 20);
1556	}
1557
1558	/// Tests `TryTrampoline::tail_rec_m` with `Rc<T>`.
1559	///
1560	/// Verifies that stack-safe fallible recursion works with `!Send` types.
1561	#[test]
1562	fn test_try_trampoline_tail_rec_m_with_rc() {
1563		use std::rc::Rc;
1564
1565		let task: TryTrampoline<Rc<u64>, Rc<String>> = TryTrampoline::tail_rec_m(
1566			|(n, acc): (u64, Rc<u64>)| {
1567				if n == 0 {
1568					TryTrampoline::ok(ControlFlow::Break(acc))
1569				} else {
1570					TryTrampoline::ok(ControlFlow::Continue((n - 1, Rc::new(*acc + n))))
1571				}
1572			},
1573			(100u64, Rc::new(0u64)),
1574		);
1575		assert_eq!(*task.evaluate().unwrap(), 5050);
1576	}
1577
1578	/// Tests `catch_unwind` on `TryTrampoline`.
1579	///
1580	/// Verifies that panics are caught and converted to `Err(String)`.
1581	#[test]
1582	fn test_catch_unwind() {
1583		let task = TryTrampoline::<i32, String>::catch_unwind(|| {
1584			if true {
1585				panic!("oops")
1586			}
1587			42
1588		});
1589		assert_eq!(task.evaluate(), Err("oops".to_string()));
1590	}
1591
1592	/// Tests `catch_unwind` on `TryTrampoline` with a non-panicking closure.
1593	///
1594	/// Verifies that a successful closure wraps the value in `Ok`.
1595	#[test]
1596	fn test_catch_unwind_success() {
1597		let task = TryTrampoline::<i32, String>::catch_unwind(|| 42);
1598		assert_eq!(task.evaluate(), Ok(42));
1599	}
1600
1601	/// Tests `TryTrampoline::catch_unwind_with` with a panicking closure.
1602	///
1603	/// Verifies that the custom handler converts the panic payload.
1604	#[test]
1605	fn test_catch_unwind_with_panic() {
1606		let task = TryTrampoline::<i32, i32>::catch_unwind_with(
1607			|| {
1608				if true {
1609					panic!("oops")
1610				}
1611				42
1612			},
1613			|_payload| -1,
1614		);
1615		assert_eq!(task.evaluate(), Err(-1));
1616	}
1617
1618	/// Tests `TryTrampoline::catch_unwind_with` with a non-panicking closure.
1619	///
1620	/// Verifies that a successful closure wraps the value in `Ok`.
1621	#[test]
1622	fn test_catch_unwind_with_success() {
1623		let task = TryTrampoline::<i32, i32>::catch_unwind_with(|| 42, |_payload| -1);
1624		assert_eq!(task.evaluate(), Ok(42));
1625	}
1626
1627	// QuickCheck Law Tests (via inherent methods)
1628
1629	// Functor Laws
1630
1631	/// Functor identity: `ok(a).map(identity) == Ok(a)`.
1632	#[quickcheck]
1633	fn functor_identity(x: i32) -> bool {
1634		TryTrampoline::<i32, i32>::ok(x).map(|a| a).evaluate() == Ok(x)
1635	}
1636
1637	/// Functor composition: `t.map(f . g) == t.map(g).map(f)`.
1638	#[quickcheck]
1639	fn functor_composition(x: i32) -> bool {
1640		let f = |a: i32| a.wrapping_add(1);
1641		let g = |a: i32| a.wrapping_mul(2);
1642		let lhs = TryTrampoline::<i32, i32>::ok(x).map(move |a| f(g(a))).evaluate();
1643		let rhs = TryTrampoline::<i32, i32>::ok(x).map(g).map(f).evaluate();
1644		lhs == rhs
1645	}
1646
1647	// Monad Laws
1648
1649	/// Monad left identity: `ok(a).bind(f) == f(a)`.
1650	#[quickcheck]
1651	fn monad_left_identity(a: i32) -> bool {
1652		let f = |x: i32| TryTrampoline::<i32, i32>::ok(x.wrapping_mul(2));
1653		TryTrampoline::ok(a).bind(f).evaluate() == f(a).evaluate()
1654	}
1655
1656	/// Monad right identity: `m.bind(ok) == m`.
1657	#[quickcheck]
1658	fn monad_right_identity(x: i32) -> bool {
1659		TryTrampoline::<i32, i32>::ok(x).bind(TryTrampoline::ok).evaluate() == Ok(x)
1660	}
1661
1662	/// Monad associativity: `m.bind(f).bind(g) == m.bind(|a| f(a).bind(g))`.
1663	#[quickcheck]
1664	fn monad_associativity(x: i32) -> bool {
1665		let f = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_add(1));
1666		let g = |a: i32| TryTrampoline::<i32, i32>::ok(a.wrapping_mul(3));
1667		let lhs = TryTrampoline::<i32, i32>::ok(x).bind(f).bind(g).evaluate();
1668		let rhs = TryTrampoline::<i32, i32>::ok(x).bind(move |a| f(a).bind(g)).evaluate();
1669		lhs == rhs
1670	}
1671
1672	/// Error short-circuit: `err(e).bind(f).evaluate() == Err(e)`.
1673	#[quickcheck]
1674	fn error_short_circuit(e: i32) -> bool {
1675		TryTrampoline::<i32, i32>::err(e).bind(|x| TryTrampoline::ok(x.wrapping_add(1))).evaluate()
1676			== Err(e)
1677	}
1678
1679	// Semigroup / Monoid tests
1680
1681	/// Tests Semigroup::append with two successful computations.
1682	#[test]
1683	fn test_semigroup_append_both_ok() {
1684		use crate::classes::Semigroup;
1685
1686		let a: TryTrampoline<String, ()> = TryTrampoline::ok("hello".to_string());
1687		let b: TryTrampoline<String, ()> = TryTrampoline::ok(" world".to_string());
1688		let result = Semigroup::append(a, b);
1689		assert_eq!(result.evaluate(), Ok("hello world".to_string()));
1690	}
1691
1692	/// Tests Semigroup::append propagates the first error.
1693	#[test]
1694	fn test_semigroup_append_first_err() {
1695		use crate::classes::Semigroup;
1696
1697		let a: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1698		let b: TryTrampoline<String, String> = TryTrampoline::ok(" world".to_string());
1699		let result = Semigroup::append(a, b);
1700		assert_eq!(result.evaluate(), Err("fail".to_string()));
1701	}
1702
1703	/// Tests Semigroup::append propagates the second error.
1704	#[test]
1705	fn test_semigroup_append_second_err() {
1706		use crate::classes::Semigroup;
1707
1708		let a: TryTrampoline<String, String> = TryTrampoline::ok("hello".to_string());
1709		let b: TryTrampoline<String, String> = TryTrampoline::err("fail".to_string());
1710		let result = Semigroup::append(a, b);
1711		assert_eq!(result.evaluate(), Err("fail".to_string()));
1712	}
1713
1714	/// Tests Semigroup associativity law for TryTrampoline.
1715	#[quickcheck]
1716	fn semigroup_associativity(
1717		a: String,
1718		b: String,
1719		c: String,
1720	) -> bool {
1721		use crate::classes::Semigroup;
1722
1723		let lhs = Semigroup::append(
1724			Semigroup::append(
1725				TryTrampoline::<String, ()>::ok(a.clone()),
1726				TryTrampoline::ok(b.clone()),
1727			),
1728			TryTrampoline::ok(c.clone()),
1729		)
1730		.evaluate();
1731		let rhs = Semigroup::append(
1732			TryTrampoline::<String, ()>::ok(a),
1733			Semigroup::append(TryTrampoline::ok(b), TryTrampoline::ok(c)),
1734		)
1735		.evaluate();
1736		lhs == rhs
1737	}
1738
1739	/// Tests Monoid::empty returns Ok with the monoidal identity.
1740	#[test]
1741	fn test_monoid_empty() {
1742		use crate::classes::Monoid;
1743
1744		let e: TryTrampoline<String, ()> = Monoid::empty();
1745		assert_eq!(e.evaluate(), Ok(String::new()));
1746	}
1747
1748	/// Tests Monoid left identity law.
1749	#[quickcheck]
1750	fn monoid_left_identity(a: String) -> bool {
1751		use crate::classes::{
1752			Monoid,
1753			Semigroup,
1754		};
1755
1756		let lhs = Semigroup::append(Monoid::empty(), TryTrampoline::<String, ()>::ok(a.clone()))
1757			.evaluate();
1758		lhs == Ok(a)
1759	}
1760
1761	/// Tests Monoid right identity law.
1762	#[quickcheck]
1763	fn monoid_right_identity(a: String) -> bool {
1764		use crate::classes::{
1765			Monoid,
1766			Semigroup,
1767		};
1768
1769		let lhs = Semigroup::append(TryTrampoline::<String, ()>::ok(a.clone()), Monoid::empty())
1770			.evaluate();
1771		lhs == Ok(a)
1772	}
1773
1774	// bimap tests
1775
1776	/// Tests bimap on a successful computation.
1777	#[test]
1778	fn test_bimap_ok() {
1779		let task: TryTrampoline<i32, String> = TryTrampoline::ok(10);
1780		let result = task.bimap(|x| x * 2, |e| e.len());
1781		assert_eq!(result.evaluate(), Ok(20));
1782	}
1783
1784	/// Tests bimap on a failed computation.
1785	#[test]
1786	fn test_bimap_err() {
1787		let task: TryTrampoline<i32, String> = TryTrampoline::err("hello".to_string());
1788		let result = task.bimap(|x| x * 2, |e| e.len());
1789		assert_eq!(result.evaluate(), Err(5));
1790	}
1791
1792	/// Tests bimap composes with map and map_err.
1793	#[quickcheck]
1794	fn bimap_consistent_with_map_and_map_err(x: i32) -> bool {
1795		let f = |a: i32| a.wrapping_add(1);
1796		let g = |e: i32| e.wrapping_mul(2);
1797
1798		let via_bimap = TryTrampoline::<i32, i32>::ok(x).bimap(f, g).evaluate();
1799		let via_map = TryTrampoline::<i32, i32>::ok(x).map(f).evaluate();
1800		via_bimap == via_map
1801	}
1802
1803	/// Tests bimap on error is consistent with map_err.
1804	#[quickcheck]
1805	fn bimap_err_consistent_with_map_err(e: i32) -> bool {
1806		let f = |a: i32| a.wrapping_add(1);
1807		let g = |e: i32| e.wrapping_mul(2);
1808
1809		let via_bimap = TryTrampoline::<i32, i32>::err(e).bimap(f, g).evaluate();
1810		let via_map_err = TryTrampoline::<i32, i32>::err(e).map_err(g).evaluate();
1811		via_bimap == via_map_err
1812	}
1813
1814	// into_rc_try_lazy / into_arc_try_lazy tests
1815
1816	/// Tests `TryTrampoline::into_rc_try_lazy` with a successful computation.
1817	///
1818	/// Verifies that the returned `RcTryLazy` evaluates to the same result and
1819	/// memoizes it (the computation runs at most once).
1820	#[test]
1821	fn test_into_rc_try_lazy_ok() {
1822		let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1823		let lazy = task.into_rc_try_lazy();
1824		assert_eq!(lazy.evaluate(), Ok(&42));
1825		// Second access returns the cached value.
1826		assert_eq!(lazy.evaluate(), Ok(&42));
1827	}
1828
1829	/// Tests `TryTrampoline::into_rc_try_lazy` with a failed computation.
1830	///
1831	/// Verifies that the returned `RcTryLazy` memoizes the error result.
1832	#[test]
1833	fn test_into_rc_try_lazy_err() {
1834		let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1835		let lazy = task.into_rc_try_lazy();
1836		assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1837	}
1838
1839	/// Tests `TryTrampoline::into_arc_try_lazy` with a successful computation.
1840	///
1841	/// Verifies that the returned `ArcTryLazy` evaluates to the same result.
1842	/// `into_arc_try_lazy` evaluates eagerly because the inner closures are not
1843	/// `Send`, so the result is stored immediately.
1844	#[test]
1845	fn test_into_arc_try_lazy_ok() {
1846		let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1847		let lazy = task.into_arc_try_lazy();
1848		assert_eq!(lazy.evaluate(), Ok(&42));
1849		// Second access returns the cached value.
1850		assert_eq!(lazy.evaluate(), Ok(&42));
1851	}
1852
1853	/// Tests `TryTrampoline::into_arc_try_lazy` with a failed computation.
1854	///
1855	/// Verifies that the returned `ArcTryLazy` memoizes the error result.
1856	#[test]
1857	fn test_into_arc_try_lazy_err() {
1858		let task: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1859		let lazy = task.into_arc_try_lazy();
1860		assert_eq!(lazy.evaluate(), Err(&"oops".to_string()));
1861	}
1862
1863	/// Tests `TryTrampoline::catch_with` stack safety.
1864	///
1865	/// Verifies that deeply chained `catch_with` calls do not overflow the stack.
1866	#[test]
1867	fn test_catch_with_stack_safety() {
1868		let n = 100_000u64;
1869		let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1870		for i in 1 ..= n {
1871			task = task.catch_with(move |_| TryTrampoline::err(i));
1872		}
1873		assert_eq!(task.evaluate(), Err(n));
1874	}
1875
1876	/// Tests `TryTrampoline::catch_with` stack safety on the success path.
1877	///
1878	/// Verifies that a deeply chained `catch_with` that eventually succeeds
1879	/// does not overflow the stack.
1880	#[test]
1881	fn test_catch_with_stack_safety_ok() {
1882		let n = 100_000u64;
1883		let mut task: TryTrampoline<u64, u64> = TryTrampoline::err(0);
1884		for i in 1 .. n {
1885			task = task.catch_with(move |_| TryTrampoline::err(i));
1886		}
1887		task = task.catch_with(|e| TryTrampoline::ok(e));
1888		assert_eq!(task.evaluate(), Ok(n - 1));
1889	}
1890
1891	/// Tests `TryTrampoline::resume` on a successful pure value.
1892	///
1893	/// Verifies that resuming a completed `TryTrampoline::ok` returns `Ok(Ok(value))`.
1894	#[test]
1895	fn test_resume_ok() {
1896		let t: TryTrampoline<i32, String> = TryTrampoline::ok(42);
1897		assert_eq!(t.resume().unwrap(), Ok(42));
1898	}
1899
1900	/// Tests `TryTrampoline::resume` on a failed pure value.
1901	///
1902	/// Verifies that resuming a completed `TryTrampoline::err` returns `Ok(Err(error))`.
1903	#[test]
1904	fn test_resume_err() {
1905		let t: TryTrampoline<i32, String> = TryTrampoline::err("oops".to_string());
1906		assert_eq!(t.resume().unwrap(), Err("oops".to_string()));
1907	}
1908
1909	/// Tests `TryTrampoline::resume` on a deferred computation.
1910	///
1911	/// Verifies that resuming a deferred `TryTrampoline` returns `Err(thunk)`,
1912	/// and evaluating the thunk yields another `TryTrampoline` that can be resumed.
1913	#[test]
1914	fn test_resume_deferred() {
1915		let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(99));
1916		match t.resume() {
1917			Ok(_) => panic!("expected suspension"),
1918			Err(thunk) => {
1919				let next = thunk.evaluate();
1920				assert_eq!(next.resume().unwrap(), Ok(99));
1921			}
1922		}
1923	}
1924
1925	/// Tests that resuming through a chain of deferred steps eventually reaches `Ok`.
1926	///
1927	/// Builds a chain of three deferred steps and manually drives it to completion.
1928	#[test]
1929	fn test_resume_chain_reaches_ok() {
1930		let t: TryTrampoline<i32, String> = TryTrampoline::defer(|| {
1931			TryTrampoline::defer(|| TryTrampoline::defer(|| TryTrampoline::ok(7)))
1932		});
1933
1934		let mut current = t;
1935		let mut steps = 0;
1936		loop {
1937			match current.resume() {
1938				Ok(result) => {
1939					assert_eq!(result, Ok(7));
1940					break;
1941				}
1942				Err(thunk) => {
1943					current = thunk.evaluate();
1944					steps += 1;
1945				}
1946			}
1947		}
1948		assert!(steps > 0, "expected at least one suspension step");
1949	}
1950}