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