diff --git a/benches/coroutine.rs b/benches/coroutine.rs index 1321aa5c..1c7d4c8b 100644 --- a/benches/coroutine.rs +++ b/benches/coroutine.rs @@ -1,5 +1,3 @@ -use core::pin::pin; - use corosensei::stack::DefaultStack; use corosensei::{on_stack, Coroutine, ScopedCoroutine}; use criterion::measurement::Measurement; @@ -25,12 +23,12 @@ fn coroutine_call(name: &str, c: &mut Criterion) { c.bench_function(name, move |b| { b.iter(|| { - let identity = pin!(ScopedCoroutine::::with_stack( + let coroutine = ScopedCoroutine::::with_stack( &mut stack, - |_yielder, input| input - )); - identity.resume(black_box(0usize)); - }) + |_yielder, input| input, + ); + coroutine.scope(|mut identity| identity.resume(black_box(0usize))); + }); }); } diff --git a/src/coroutine.rs b/src/coroutine.rs index 570e08e5..193b8823 100644 --- a/src/coroutine.rs +++ b/src/coroutine.rs @@ -159,7 +159,7 @@ impl Coroutine { /// This function returns a `Coroutine` which, when resumed, will execute /// `func` to completion. When desired the `func` can suspend itself via /// `Yielder::suspend`. - pub fn new(f: F) -> Self + pub fn new(func: F) -> Self where F: FnOnce(&Yielder, Input) -> Return, F: 'static, @@ -167,7 +167,7 @@ impl Coroutine { Yield: 'static, Return: 'static, { - Self::with_stack(Default::default(), f) + Self::with_stack(Default::default(), func) } } @@ -177,7 +177,7 @@ impl Coroutine(stack: Stack, f: F) -> Self + pub fn with_stack(stack: Stack, func: F) -> Self where F: FnOnce(&Yielder, Input) -> Return, F: 'static, @@ -186,12 +186,12 @@ impl Coroutine(stack: Stack, f: F) -> Self + pub(crate) unsafe fn with_stack_unchecked(stack: Stack, func: F) -> Self where F: FnOnce(&Yielder, Input) -> Return, { @@ -255,7 +255,8 @@ impl Coroutine, f); + let stack_ptr = + arch::init_stack(&stack, coroutine_func::, func); let sanitizer_fiber = stack.sanitizer_fiber(); Self { stack, diff --git a/src/lib.rs b/src/lib.rs index 2a3f81af..89991460 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -250,7 +250,7 @@ pub mod trap; mod util; pub use coroutine::{on_stack, Coroutine, CoroutineResult, Yielder}; -pub use scoped::ScopedCoroutine; +pub use scoped::{ScopedCoroutine, ScopedCoroutineRef}; #[cfg(test)] mod tests; diff --git a/src/scoped.rs b/src/scoped.rs index c672c9cb..e8bff512 100644 --- a/src/scoped.rs +++ b/src/scoped.rs @@ -1,9 +1,10 @@ +use core::marker::PhantomData; + +use crate::stack; #[cfg(feature = "default-stack")] use crate::stack::DefaultStack; -use crate::Yielder; -use crate::{stack, trap::CoroutineTrapHandler}; -use crate::{Coroutine, CoroutineResult}; -use core::pin::Pin; +use crate::trap::CoroutineTrapHandler; +use crate::{Coroutine, CoroutineResult, Yielder}; /// Variant of [`Coroutine`] which allows the use of non-`'static` lifetimes. /// @@ -12,14 +13,44 @@ use core::pin::Pin; /// - Lifetimes in `Input`, `Yield` and `Return`, /// - Borrowed values in the coroutine body closure. /// -/// However this requires that the coroutine itself be pinned to ensure that it -/// is properly dropped before any of these lifetimes end. The easiest way to -/// do so it to use the [`pin!`] macro when constructing the coroutine +/// To ensure safety, this requires that the coroutine only be accessed within +/// a scope, at the end of which it is dropped. Within the scope, a +/// [`ScopedCoroutineRef`] is provided, which mirrors the API of +/// `Coroutine`. +/// +/// # Example +/// +/// ``` +/// use corosensei::{ScopedCoroutine, Yielder}; /// -/// [`pin!`]: core::pin::pin +/// let mut counter = 0; +/// let coroutine = ScopedCoroutine::new(|yielder: &Yielder, input| { +/// if input == 0 { +/// return; +/// } +/// counter += input; +/// loop { +/// let input = yielder.suspend(()); +/// if input == 0 { +/// break; +/// } +/// counter += input; +/// } +/// }); +/// coroutine.scope(|mut coroutine| { +/// coroutine.as_mut().resume(1); +/// coroutine.as_mut().resume(2); +/// coroutine.as_mut().resume(3); +/// coroutine.as_mut().resume(4); +/// coroutine.as_mut().resume(5); +/// }); +/// assert_eq!(counter, 15); +/// ``` #[cfg(not(feature = "default-stack"))] -pub struct ScopedCoroutine { +pub struct ScopedCoroutine<'a, Input, Yield, Return, Stack: stack::Stack> { inner: Coroutine, + // Ensure any borrows in the coroutine body outlive the ScopedCoroutine. + marker: PhantomData<&'a mut ()>, } /// Variant of [`Coroutine`] which allows the use of non-`'static` lifetimes. @@ -29,48 +60,125 @@ pub struct ScopedCoroutine { /// - Lifetimes in `Input`, `Yield` and `Return`, /// - Borrowed values in the coroutine body closure. /// -/// However this requires that the coroutine itself be pinned to ensure that it -/// is properly dropped before any of these lifetimes end. The easiest way to -/// do so it to use the [`pin!`] macro when constructing the coroutine +/// To ensure safety, this requires that the coroutine only be accessed within +/// a scope, at the end of which it is dropped. Within the scope, a +/// [`ScopedCoroutineRef`] is provided, which mirrors the API of +/// `Coroutine`. +/// +/// # Example +/// +/// ``` +/// use corosensei::ScopedCoroutine; /// -/// [`pin!`]: core::pin::pin +/// let mut counter = 0; +/// let coroutine = ScopedCoroutine::new(|yielder, input| { +/// if input == 0 { +/// return; +/// } +/// counter += input; +/// loop { +/// let input = yielder.suspend(()); +/// if input == 0 { +/// break; +/// } +/// counter += input; +/// } +/// }); +/// coroutine.scope(|mut coroutine| { +/// coroutine.as_mut().resume(1); +/// coroutine.as_mut().resume(2); +/// coroutine.as_mut().resume(3); +/// coroutine.as_mut().resume(4); +/// coroutine.as_mut().resume(5); +/// }); +/// assert_eq!(counter, 15); +/// ``` #[cfg(feature = "default-stack")] -pub struct ScopedCoroutine { +pub struct ScopedCoroutine<'a, Input, Yield, Return, Stack: stack::Stack = DefaultStack> { inner: Coroutine, + // Ensure any borrows in the coroutine body outlive the ScopedCoroutine. + marker: PhantomData<&'a mut ()>, } #[cfg(feature = "default-stack")] -impl ScopedCoroutine { - /// Creates a new coroutine which will execute `func` on a new stack. +impl<'a, Input, Yield, Return> ScopedCoroutine<'a, Input, Yield, Return, DefaultStack> { + /// Creates a new coroutine which will execute `func` on the given stack. /// - /// This function returns a `ScopedCoroutine` which, when resumed, will - /// execute `func` to completion. When desired the `func` can suspend - /// itself via `Yielder::suspend`. - pub fn new(f: F) -> Self + /// This function creates a coroutine which, when resumed, will execute + /// `body` to completion. When desired the `func` can suspend itself via + /// [`Yielder::suspend`]. + pub fn new(func: F) -> Self where F: FnOnce(&Yielder, Input) -> Return, { - Self::with_stack(Default::default(), f) + Self::with_stack(Default::default(), func) } } -impl ScopedCoroutine { +impl<'a, Input, Yield, Return, Stack: stack::Stack> + ScopedCoroutine<'a, Input, Yield, Return, Stack> +{ /// Creates a new coroutine which will execute `func` on the given stack. /// - /// This function returns a coroutine which, when resumed, will execute - /// `func` to completion. When desired the `func` can suspend itself via + /// This function creates a coroutine which, when resumed, will execute + /// `body` to completion. When desired the `func` can suspend itself via /// [`Yielder::suspend`]. - pub fn with_stack(stack: Stack, f: F) -> Self + pub fn with_stack(stack: Stack, func: F) -> Self where F: FnOnce(&Yielder, Input) -> Return, { - unsafe { - Self { - inner: Coroutine::with_stack_unchecked(stack, f), - } + Self { + inner: unsafe { Coroutine::with_stack_unchecked(stack, func) }, + marker: PhantomData, } } + /// Extracts the stack from a coroutine that has finished executing. + /// + /// This allows the stack to be re-used for another coroutine. + pub fn into_stack(self) -> Stack { + self.inner.into_stack() + } + + /// Creates a scope in which the coroutine can be executed. + /// + /// This ensure that any lifetimes used by the coroutine do not escape the + /// scope and that the coroutine is dropped at the end of the scope. + pub fn scope(mut self, f: F) -> T + where + F: FnOnce(ScopedCoroutineRef<'_, Input, Yield, Return, Stack>) -> T, + { + let coroutine = ScopedCoroutineRef { + inner: &mut self.inner, + }; + f(coroutine) + } +} + +/// Reference to a coroutine within a scope created by +/// [`ScopedCoroutine::scope`]. +/// +/// Note that, unlike normal mutable references, Rust will not automatically +/// re-borrow this type so you may need to use the +/// [`ScopedCoroutineRef::as_mut`] method when invoking methods that take a +/// mutable reference. +pub struct ScopedCoroutineRef<'a, Input, Yield, Return, Stack: stack::Stack> { + inner: &'a mut Coroutine, +} + +impl + ScopedCoroutineRef<'_, Input, Yield, Return, Stack> +{ + /// Reborrows the `ScopedCoroutineRef`. + /// + /// This is useful when the reference needs to be used multiple times. + /// + /// This is necessary before Rust only supports automatic reborrowing for + /// plain mutable references. + pub fn as_mut(&mut self) -> ScopedCoroutineRef<'_, Input, Yield, Return, Stack> { + ScopedCoroutineRef { inner: self.inner } + } + /// Resumes execution of this coroutine. /// /// This function will transfer execution to the coroutine and resume from @@ -88,8 +196,8 @@ impl ScopedCoroutine, val: Input) -> CoroutineResult { - unsafe { self.get_unchecked_mut().inner.resume(val) } + pub fn resume(&mut self, val: Input) -> CoroutineResult { + self.inner.resume(val) } /// Returns whether this coroutine has been resumed at least once. @@ -115,8 +223,8 @@ impl ScopedCoroutine) { - unsafe { self.get_unchecked_mut().inner.force_reset() } + pub unsafe fn force_reset(&mut self) { + self.inner.force_reset() } /// Unwinds the coroutine stack, dropping any live objects that are @@ -137,15 +245,8 @@ impl ScopedCoroutine) { - unsafe { self.get_unchecked_mut().inner.force_unwind() } - } - - /// Extracts the stack from a coroutine that has finished executing. - /// - /// This allows the stack to be re-used for another coroutine. - pub fn into_stack(self) -> Stack { - self.inner.into_stack() + pub fn force_unwind(&mut self) { + self.inner.force_unwind() } /// Returns a [`CoroutineTrapHandler`] which can be used to handle traps that