,
{
- /// Creates a new unopened scope.
- fn new() -> Self {
- let b = Box::new(Scope::new());
- let b = Box::leak(b);
- Self(b.into())
- }
-
- /// Opens this scope, making it possible to call [`BoxScope::enter`] on the scope.
+ /// Ties the passed scope to the heap.
+ ///
+ /// This function erased the `Future` generic type of the [`TopScope`], at the cost
+ /// of using a dynamic function call to poll the future.
+ ///
+ /// If the `Future` generic type can be inferred, it can be more efficient to use [`BoxScope::new_typed`].
///
/// # Panics
///
- /// - If `producer` panics.
- fn open(self, producer: P) -> BoxScope
+ /// - If `scope` panics.
+ pub fn new_erased>(scope: S) -> Self
where
- P: FnOnce(TimeCapsule) -> F,
+ S::Future: 'static,
{
- // SAFETY: `self.0` is dereference-able due to coming from a `Box`.
- unsafe { Scope::open(self.0, producer) }
-
- let open_scope = BoxScope(self.0);
-
- // SAFETY: don't call drop on self to avoid double-free since the resource of self was moved to `open_scope`
- std::mem::forget(self);
-
- open_scope
- }
-}
-
-impl Drop for BoxScope
-where
- T: for<'a> Family<'a>,
- F: Future,
-{
- fn drop(&mut self) {
- // SAFETY: created from a Box in the constructor, so dereference-able.
- let this = unsafe { self.0.as_ref() };
- // SAFETY: we MUST release the `RefMut` before calling drop on the `Box` otherwise we'll call its
- // destructor after releasing its backing memory, causing uaf
- {
- let mut fut = this.active_fut.borrow_mut();
- // unwrap: fut was set in open
- let fut = fut.as_mut().unwrap();
- unsafe { ManuallyDrop::drop(fut) };
- }
- unsafe { drop(Box::from_raw(self.0.as_ptr())) }
+ let this = mem::ManuallyDrop::new(BoxScope::new_typed(scope));
+ Self(this.0)
}
}
@@ -89,19 +66,59 @@ where
T: for<'a> Family<'a>,
F: Future,
{
- /// Creates a new scope from a producer.
+ /// Ties the passed scope to the heap.
+ ///
+ /// This function retains the `Future` generic type from the [`TopScope`].
+ /// To store the [`BoxScope`] in a struct, it can be easier to use [`BoxScope::new_erased`].
///
/// # Panics
///
- /// - If `producer` panics.
- pub fn new(producer: P) -> BoxScope
+ /// - If `scope` panics.
+ pub fn new_typed>(scope: S) -> BoxScope
where
- P: FnOnce(TimeCapsule) -> F,
+ S: TopScope,
{
- let scope = ClosedBoxScope::new();
- scope.open(producer)
+ let raw_scope = Box::new(RawScope::::new_uninit());
+ let raw_scope: *mut RawScope> = Box::into_raw(raw_scope);
+ struct Guard {
+ raw_scope: *mut Sc,
+ }
+ // guard ensures Box is freed on panic (i.e. if scope.run panics)
+ let panic_guard = Guard { raw_scope };
+ impl Drop for Guard {
+ fn drop(&mut self) {
+ // SAFETY: defuse below makes sure this only happens on panic,
+ // in this case, self.raw_scope is still in the same uninitialized state
+ // and not otherwise being cleaned up, so this `Box::from_raw` pairs with
+ // `Box::into_raw` above
+ drop(unsafe { Box::from_raw(self.raw_scope) })
+ }
+ }
+
+ let raw_scope: *mut RawScope = raw_scope.cast();
+
+ // SAFETY:
+ // 1. `raw_scope` allocated by the `Box` so is valid memory, although the future is not yet initialized
+ // 2. `raw_scope` was created from a valid `RawScope::>`, so `state` is fully initialized.
+ //
+ // Note: as a post-condition of `RawScope`, `raw_scope` is fully initialized.
+ unsafe {
+ RawScope::open(raw_scope, scope);
+ }
+
+ mem::forget(panic_guard); // defuse guard
+ // (guard field has no drop glue, so this does not leak anything, it just skips the above `Drop` impl)
+
+ // SAFETY: `raw_scope` allocated by the `Box` so is non-null.
+ BoxScope(unsafe { NonNull::new_unchecked(raw_scope) })
}
+}
+impl BoxScope
+where
+ T: for<'a> Family<'a>,
+ F: Future,
+{
/// Enters the scope, making it possible to access the data frozen inside of the scope.
///
/// # Panics
@@ -113,21 +130,10 @@ where
where
G: for<'a> FnOnce(&'a mut >::Family) -> Output,
{
- // SAFETY: `self.0` is dereference-able due to coming from a `Box`.
- unsafe { Scope::enter(self.0, f) }
- }
-}
-
-impl crate::DynBoxScope
-where
- T: for<'a> Family<'a>,
-{
- /// Convenient function to create a new pinned scope from a producer.
- pub fn pin(producer: P) -> Self
- where
- P: FnOnce(TimeCapsule) -> F,
- F: Future + 'static,
- {
- Self::new(|time_capsule| Box::pin(producer(time_capsule)))
+ // SAFETY:
+ // 1. `self.0` is valid as a post-condition of `new_typed`.
+ // 2. The object pointed to by `self.0` did not move and won't before deallocation.
+ // 3. `BoxScope::enter` takes an exclusive reference and the reference passed to `f` cannot escape `f`.
+ unsafe { RawScope::enter(self.0, f) }
}
}
diff --git a/src/counterexamples.rs b/src/counterexamples.rs
index e7f9c87..2ba22f7 100644
--- a/src/counterexamples.rs
+++ b/src/counterexamples.rs
@@ -7,7 +7,7 @@
//! ## Covariant escapes to inner
//!
//! ```compile_fail,E0597
-//! use nolife::{Family, BoxScope, TimeCapsule};
+//! use nolife::{scope, BoxScope, Family};
//!
//! struct Covariant<'a> {
//! x: &'a str,
@@ -21,15 +21,13 @@
//!
//! fn covariant_inner() {
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let mut f = Covariant { x: "bbb" };
-//! loop {
-//! time_capsule.freeze(&mut f).await;
-//! println!("Called {}", f.x)
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_typed(scope!({
+//! let mut f = Covariant { x: "bbb" };
+//! loop {
+//! freeze!(&mut f);
+//! println!("Called {}", f.x)
+//! }
+//! }));
//!
//! {
//! let s = String::from("foodog");
@@ -44,8 +42,8 @@
//! ## Covariant escapes to outer
//!
//! ```compile_fail,E0521
+//! use nolife::{scope, BoxScope, Family};
//! use std::cell::Cell;
-//! use nolife::{Family, BoxScope, TimeCapsule};
//!
//! struct Covariant<'a> {
//! x: &'a str,
@@ -60,15 +58,13 @@
//! fn covariant_outer() {
//! let output = Cell::new("foo");
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let mut f = Covariant { x: "bbb" };
-//! loop {
-//! time_capsule.freeze(&mut f).await;
-//! println!("Called {}", f.x)
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_erased(scope!({
+//! let mut f = Covariant { x: "bbb" };
+//! loop {
+//! freeze!(&mut f);
+//! println!("Called {}", f.x)
+//! }
+//! }));
//!
//! {
//! scope.enter(|f| {
@@ -83,8 +79,7 @@
//! ## Covariant escapes to inner 2
//!
//! ```compile_fail,E0597
-//! use nolife::{BoxScope, Family, TimeCapsule};
-//!
+//! use nolife::{scope, BoxScope, Family};
//!
//! struct Covariant<'a> {
//! x: &'a str,
@@ -98,16 +93,14 @@
//!
//! fn box_covariant_inner() {
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let x = String::from("aaaaa");
-//! let mut f = Covariant { x: &x };
-//! loop {
-//! time_capsule.freeze(&mut f).await;
-//! println!("Called {}", f.x)
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_typed(scope!({
+//! let x = String::from("aaaaa");
+//! let mut f = Covariant { x: &x };
+//! loop {
+//! freeze!(&mut f);
+//! println!("Called {}", f.x)
+//! }
+//! }));
//!
//! {
//! let s = String::from("dangling");
@@ -115,13 +108,12 @@
//! }
//! };
//! }
-
//! ```
//!
//! ## Covariant escapes to outer 2
//!
//! ```compile_fail
-//! use nolife::{BoxScope, Family, TimeCapsule};
+//! use nolife::{scope, BoxScope, Family};
//! use std::cell::Cell;
//!
//! struct Covariant<'a> {
@@ -137,16 +129,14 @@
//! fn box_covariant_outer() {
//! let outer = Cell::new("foo");
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let x = String::from("aaaaa");
-//! let mut f = Covariant { x: &x };
-//! loop {
-//! time_capsule.freeze(&mut f).await;
-//! println!("Called {}", f.x)
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_typed(scope!({
+//! let x = String::from("aaaaa");
+//! let mut f = Covariant { x: &x };
+//! loop {
+//! freeze!(&mut f);
+//! println!("Called {}", f.x)
+//! }
+//! }));
//!
//! let inner = scope.enter(|f| f.x);
//! outer.set(inner);
@@ -158,7 +148,7 @@
//! ## Covariant with `Drop`
//!
//! ```compile_fail,E0597
-//! use nolife::{Family, BoxScope, TimeCapsule};
+//! use nolife::{scope, BoxScope, Family};
//!
//! struct CovariantDrop<'a> {
//! x: &'a str,
@@ -177,19 +167,16 @@
//!
//! fn covariant_drop() {
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let mut f = CovariantDrop { x: "inner" };
-//! loop {
-//! println!("Called {}", f.x);
-//! time_capsule.freeze(&mut f).await;
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_typed(scope!({
+//! let mut f = CovariantDrop { x: "inner" };
+//! loop {
+//! println!("Called {}", f.x);
+//! freeze!(&mut f);
+//! }
+//! }));
//!
//! let outer = String::from("outer");
//!
-//!
//! {
//! scope.enter(|f| {
//! f.x = &outer;
@@ -218,19 +205,17 @@
//! let outer: Cell<&str> = Cell::new("toto");
//!
//! {
-//! let mut scope = nolife::BoxScope::new(
-//! |mut time_capsule: nolife::TimeCapsule| async move {
-//! loop {
-//! let mut x = String::from("inner");
-//!
-//! let mut f = Contravariant {
-//! f: Box::new(|_| {}),
-//! };
-//! time_capsule.freeze(&mut f).await;
-//! (f.f)(&mut x);
-//! }
-//! },
-//! );
+//! let mut scope = nolife::BoxScope::::new_typed(nolife::scope!({
+//! loop {
+//! let mut x = String::from("inner");
+//!
+//! let mut f = Contravariant {
+//! f: Box::new(|_| {}),
+//! };
+//! freeze!(&mut f);
+//! (f.f)(&mut x);
+//! }
+//! }));
//!
//! scope.enter(|f| {
//! f.f = Box::new(|inner| outer.set(inner));
@@ -243,7 +228,7 @@
//! ## Covariant coming from a previous scope
//!
//! ```compile_fail,E0597
-//! use nolife::{Family, BoxScope, TimeCapsule};
+//! use nolife::{scope, BoxScope, Family};
//!
//! struct Covariant<'a> {
//! x: &'a str,
@@ -255,18 +240,15 @@
//! type Family = Covariant<'a>;
//! }
//!
-//!
//! fn covariant_inner() {
//! {
-//! let mut scope = BoxScope::new(
-//! |mut time_capsule: TimeCapsule| async move {
-//! let mut f = Covariant { x: "bbb" };
-//! loop {
-//! time_capsule.freeze(&mut f).await;
-//! println!("Called {}", f.x)
-//! }
-//! },
-//! );
+//! let mut scope = BoxScope::::new_erased(scope!({
+//! let mut f = Covariant { x: "bbb" };
+//! loop {
+//! freeze!(&mut f);
+//! println!("Called {}", f.x)
+//! }
+//! }));
//! {
//! let s = String::from("foodog");
//!
@@ -276,7 +258,84 @@
//! });
//! }
//! }
-//! scope.enter(|f| ());
+//! scope.enter(|_f| ());
//! }
//! }
//! ```
+//!
+//! # Recursion is not allowed
+//!
+//! ```compile_fail,E0733
+//! use nolife::{scope, SingleFamily, TopScope};
+//!
+//! fn recursive_sub_scope() {
+//! fn some_scope(x: u32) -> impl TopScope> {
+//! scope!({
+//! if x == 0 {
+//! freeze_forever!(&mut 0)
+//! } else {
+//! sub_scope!(some_scope(x - 1));
+//! freeze_forever!(&mut 0)
+//! }
+//! })
+//! }
+//! }
+//! ```
+//!
+//! # Attempting to save the frozen future in an async block
+//!
+//! ```compile_fail,E0767,E0267
+//! use nolife::{scope, SingleFamily, TopScope};
+//! fn forcing_inner_async() {
+//! fn some_scope(x: u32) -> impl TopScope> {
+//! scope!({
+//! let fut = async {
+//! freeze!(&mut 0);
+//! };
+//! // poll future
+//! // bang!
+//! panic!()
+//! })
+//! }
+//! }
+//! ```
+//!
+//! #Â Dropping a borrowed input to a scope.
+//!
+//! ```compile_fail,E505
+//! use nolife::{scope, BoxScope, SingleFamily, TopScope};
+//!
+//! fn ref_scope() {
+//! fn scope_with_ref<'scope, 'a: 'scope>(
+//! s: &'a str,
+//! ) -> impl TopScope> + 'scope {
+//! scope!({ freeze_forever!(&mut s.len()) })
+//! }
+//! let x = "Intel the Beagle".to_string();
+//! let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x));
+//!
+//! drop(x);
+//!
+//! scope.enter(|x| assert_eq!(*x, 16));
+//! }
+//! ```
+//!
+//! #Â Dropping a borrowed input to a scope, erased version
+//!
+//! ```compile_fail,E597,E505
+//! use nolife::{scope, BoxScope, SingleFamily, TopScope};
+//!
+//! fn ref_scope() {
+//! fn scope_with_ref<'scope, 'a: 'scope>(
+//! s: &'a str,
+//! ) -> impl TopScope> + 'scope {
+//! scope!({ freeze_forever!(&mut s.len()) })
+//! }
+//! let x = "Intel the Beagle".to_string();
+//! let mut scope = BoxScope::, _>::new_erased(scope_with_ref(&x));
+//!
+//! drop(x);
+//!
+//! scope.enter(|x| assert_eq!(*x, 16));
+//! }
+//! ```
diff --git a/src/lib.rs b/src/lib.rs
index b220c63..a2d90a9 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,7 @@
#![warn(rustdoc::broken_intra_doc_links)]
#![warn(missing_docs)]
+#![deny(elided_lifetimes_in_paths)]
+#![deny(unsafe_op_in_unsafe_fn)]
#![doc = include_str!("../README.md")]
#![doc(
html_favicon_url = "https://raw.githubusercontent.com/dureuill/nolife/main/assets/nolife-tr.png?raw=true"
@@ -9,17 +11,19 @@
)]
mod box_scope;
+#[cfg(not(miri))]
pub mod counterexamples;
-mod scope;
-pub use scope::{FrozenFuture, TimeCapsule};
+mod raw_scope;
+pub mod scope;
+#[doc(hidden)]
+pub use raw_scope::{FrozenFuture, TimeCapsule};
/// From , originally from
/// [genawaiter](https://lib.rs/crates/genawaiter).
mod waker;
pub use box_scope::BoxScope;
-
-/// Convenient type alias for a [`BoxScope`] whose future is an erased boxed future.
-pub type DynBoxScope = BoxScope>>>;
+pub use scope::Scope;
+pub use scope::TopScope;
use std::marker::PhantomData;
@@ -27,7 +31,7 @@ use std::marker::PhantomData;
///
/// Since this enum has no variant, a value of this type can never actually exist.
/// This type is similar to [`std::convert::Infallible`] and used as a technicality to ensure that
-/// functions passed to [`BoxScope::new`] never return.
+/// functions passed to [`BoxScope::new_erased`] never return.
///
/// ## Future compatibility
///
@@ -50,8 +54,8 @@ pub trait Family<'a> {
/// Types that don't contain a lifetime are `'static`, and have one obvious family.
///
/// The usefulness of using `'static` types in the scopes of this crate is dubious, but should you want to do this,
-/// for any `T : 'static` pass a `TimeCapsule>` to your async function.
-struct SingleFamily(PhantomData);
+/// for any `T : 'static` you can use this family.
+pub struct SingleFamily(PhantomData);
impl<'a, T: 'static> Family<'a> for SingleFamily {
type Family = T;
}
@@ -61,15 +65,13 @@ mod test {
use super::*;
#[test]
fn produce_output() {
- let mut scope = BoxScope::new(
- |mut time_capsule: TimeCapsule>| async move {
- let mut x = 0u32;
- loop {
- time_capsule.freeze(&mut x).await;
- x += 1;
- }
- },
- );
+ let mut scope = BoxScope::, _>::new_typed(scope!({
+ let mut x = 0u32;
+ loop {
+ freeze!(&mut x);
+ x += 1;
+ }
+ }));
assert_eq!(scope.enter(|x| *x + 42), 42);
assert_eq!(scope.enter(|x| *x + 42), 43);
@@ -78,127 +80,121 @@ mod test {
}
#[test]
- fn panicking_future() {
- let mut scope = BoxScope::new(|_: TimeCapsule>| async move { panic!() });
+ fn produce_output_erased() {
+ let mut scope = BoxScope::>::new_erased(scope!({
+ let mut x = 0u32;
+ loop {
+ freeze!(&mut x);
+ x += 1;
+ }
+ }));
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- scope.enter(|x| println!("{x}"))
- })),
- Err(_)
- ));
+ assert_eq!(scope.enter(|x| *x + 42), 42);
+ assert_eq!(scope.enter(|x| *x + 42), 43);
+ scope.enter(|x| *x += 100);
+ assert_eq!(scope.enter(|x| *x + 42), 145);
+ }
+ fn must_panic(f: F)
+ where
+ F: FnOnce() -> R,
+ {
assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- scope.enter(|x| println!("{x}"))
- })),
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)),
Err(_)
));
}
+ #[test]
+ fn panicking_producer() {
+ must_panic(|| {
+ BoxScope::, _>::new_typed(unsafe {
+ crate::scope::new_scope(|_time_capsule| {
+ panic!("panicking producer");
+ #[allow(unreachable_code)]
+ async {
+ loop {}
+ }
+ })
+ })
+ });
+ }
+
+ #[test]
+ fn panicking_future() {
+ let mut scope = BoxScope::, _>::new_typed(scope!({ panic!() }));
+
+ must_panic(|| scope.enter(|x| println!("{x}")));
+ must_panic(|| scope.enter(|x| println!("{x}")));
+ }
+
#[test]
fn panicking_future_after_once() {
- let mut scope = BoxScope::new(
- |mut time_capsule: TimeCapsule>| async move {
- let mut x = 0u32;
- time_capsule.freeze(&mut x).await;
- panic!()
- },
- );
+ let mut scope = BoxScope::, _>::new_typed(scope!({
+ let mut x = 0u32;
+ freeze!(&mut x);
+ panic!()
+ }));
scope.enter(|x| println!("{x}"));
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- scope.enter(|x| println!("{x}"))
- })),
- Err(_)
- ));
-
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- scope.enter(|x| println!("{x}"))
- })),
- Err(_)
- ))
+ must_panic(|| scope.enter(|x| println!("{x}")));
+ must_panic(|| scope.enter(|x| println!("{x}")));
}
#[test]
fn panicking_enter() {
- let mut scope = BoxScope::new(
- |mut time_capsule: TimeCapsule>| async move {
- let mut x = 0u32;
- loop {
- time_capsule.freeze(&mut x).await;
- x += 1;
- }
- },
- );
+ let mut scope = BoxScope::, _>::new_typed(scope!({
+ let mut x = 0u32;
+ loop {
+ freeze!(&mut x);
+ x += 1;
+ }
+ }));
scope.enter(|x| assert_eq!(*x, 0));
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- scope.enter(|_| panic!())
- })),
- Err(_)
- ));
+ must_panic(|| scope.enter(|_| panic!()));
// '1' skipped due to panic
scope.enter(|x| assert_eq!(*x, 2));
}
#[test]
- fn cursed_time_capsule_inception() {
- struct TimeCapsuleFamily;
- impl<'a> Family<'a> for TimeCapsuleFamily {
- // Yo dawg I heard you like time capsules, so I put time capsules in your time capsules
- type Family = TimeCapsule;
+ fn ref_scope() {
+ fn scope_with_ref<'scope, 'a: 'scope>(
+ s: &'a str,
+ ) -> impl TopScope> + 'scope {
+ scope!({ freeze_forever!(&mut s.len()) })
}
+ let x = "Intel the Beagle".to_string();
+ let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x));
+
+ scope.enter(|x| assert_eq!(*x, 16));
+ }
+
+ #[test]
+ fn awaiting_in_scope_ready() {
+ let mut scope = BoxScope::>::new_erased(scope!({
+ freeze!(&mut 40);
+ std::future::ready(()).await;
+ freeze_forever!(&mut 42)
+ }));
+
+ scope.enter(|x| assert_eq!(*x, 40));
+ scope.enter(|x| assert_eq!(*x, 42));
+ }
+
+ #[test]
+ fn awaiting_in_scope_panics() {
+ let mut scope = BoxScope::>::new_erased(scope!({
+ freeze!(&mut 40);
+ let () = std::future::pending().await;
+ freeze_forever!(&mut 42)
+ }));
+
+ scope.enter(|x| assert_eq!(*x, 40));
- // we'll use this to check we panicked at the correct location, RTTI-style
- struct ReachedTheEnd;
-
- let mut outer_scope = BoxScope::new(
- |mut time_capsule: TimeCapsule| async move {
- let mut inner_scope = BoxScope::new(
- |mut inner_time_capsule: TimeCapsule| async move {
- loop {
- // very cursed
- time_capsule.freeze(&mut inner_time_capsule).await
- }
- },
- );
-
- // we're expecting a panic here; let's catch it and check we're still safe
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- inner_scope.enter(|_exchanged_time_capsule| {});
- })),
- Err(_)
- ));
-
- // we can try again
- assert!(matches!(
- std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- inner_scope.enter(|_exchanged_time_capsule| {});
- })),
- Err(_)
- ));
-
- // we can't loop here because we relinquished our time capsule to the lambda
- std::panic::panic_any(ReachedTheEnd)
- },
- );
-
- // will panic with the panic at the end
- match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- outer_scope.enter(|_time_capsule| {});
- })) {
- Ok(_) => panic!("did not panic as expected"),
- Err(panic) => panic
- .downcast::()
- .expect("panicked at the wrong location"),
- };
+ must_panic(|| scope.enter(|x| assert_eq!(*x, 42)));
}
}
diff --git a/src/raw_scope.rs b/src/raw_scope.rs
new file mode 100644
index 0000000..6c3520b
--- /dev/null
+++ b/src/raw_scope.rs
@@ -0,0 +1,246 @@
+use crate::{waker, Family, Never, TopScope};
+use std::{
+ future::Future,
+ marker::PhantomData,
+ mem::MaybeUninit,
+ pin::Pin,
+ ptr::{addr_of_mut, NonNull},
+ task::Poll,
+};
+
+/// The future resulting from using a time capsule to freeze some scope.
+pub struct FrozenFuture<'a, 'b, T>
+where
+ T: for<'c> Family<'c>,
+ 'b: 'a,
+{
+ // Using a pointer here helps ensure that while RawScope is dropped,
+ // dropping of F can't assert unique access to the .state field by
+ // operations that "touch" the FrozenFuture such moving it or passing it to a function.
+ // (This probably wasn't exploitable with the scope! macro, but it still seems
+ // more correct this way.)
+ mut_ref: State,
+ state: *mut State,
+ marker: PhantomData<&'a mut >::Family>,
+}
+
+/// Passed to the closures of a scope so that they can freeze the scope.
+pub struct TimeCapsule
+where
+ T: for<'a> Family<'a>,
+{
+ pub(crate) state: *mut State,
+}
+
+impl Clone for TimeCapsule
+where
+ T: for<'a> Family<'a>,
+{
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl Copy for TimeCapsule where T: for<'a> Family<'a> {}
+
+impl TimeCapsule
+where
+ T: for<'a> Family<'a>,
+{
+ /// Freeze a scope, making the data it has borrowed available to the outside.
+ ///
+ /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`].
+ ///
+ /// For simple cases where you don't need to execute code in the scope between two calls to `enter`,
+ /// use [`Self::freeze_forever`].
+ pub fn freeze<'a, 'b>(
+ &'a mut self,
+ t: &'a mut >::Family,
+ ) -> FrozenFuture<'a, 'b, T>
+ where
+ 'b: 'a,
+ {
+ FrozenFuture {
+ mut_ref: Some(NonNull::from(t).cast()),
+ state: self.state,
+ marker: PhantomData,
+ }
+ }
+
+ /// Freeze a scope forever, making the data it has borrowed available to the outside.
+ ///
+ /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`].
+ ///
+ /// If you need to execute code between two calls to [`crate::BoxScope::enter`], use [`Self::freeze`].
+ pub async fn freeze_forever<'a, 'b>(
+ &'a mut self,
+ t: &'a mut >::Family,
+ ) -> Never {
+ loop {
+ self.freeze(t).await
+ }
+ }
+}
+
+// This type is a pointer-type and lifetime-erased equivalent of
+// Option<&'a mut >::Family>.
+//
+// NonNull differs in variance, which would typically be corrected
+// with a `PhantomData` marker, however a projection like
+// `>::Family>` has T invariant already anyway.
+pub(crate) type State = Option>::Family>>;
+
+/// Underlying representation of a scope.
+// SAFETY: repr C to ensure conversion between RawScope> and RawScope
+// does not rely on unstable memory layout.
+#[repr(C)]
+pub(crate) struct RawScope
+where
+ T: for<'a> Family<'a>,
+{
+ state: State,
+ active_fut: F,
+}
+
+impl RawScope
+where
+ T: for<'a> Family<'a>,
+{
+ /// Creates a new closed scope.
+ pub fn new_uninit() -> RawScope> {
+ RawScope {
+ state: None,
+ active_fut: MaybeUninit::uninit(),
+ }
+ }
+}
+
+struct RawScopeFields
+where
+ T: for<'a> Family<'a>,
+{
+ state: *mut State,
+ active_fut: *mut F,
+}
+impl RawScope
+where
+ T: for<'a> Family<'a>,
+{
+ /// SAFETY:
+ ///
+ /// 1. `this` points to an allocation that can hold a `RawScope`,
+ /// not necessarily initialized or properly aligned.
+ unsafe fn fields(this: *mut Self) -> RawScopeFields {
+ RawScopeFields {
+ // SAFETY: precondition (1)
+ state: unsafe { addr_of_mut!((*this).state) },
+ // SAFETY: precondition (1)
+ active_fut: unsafe { addr_of_mut!((*this).active_fut) },
+ }
+ }
+}
+
+impl RawScope
+where
+ T: for<'a> Family<'a>,
+ F: Future,
+{
+ /// # Safety
+ ///
+ /// 1. `this` points to a properly aligned allocation that can hold a `RawScope`, where `active_fut` is not necessarily initialized.
+ /// 2. `this.state` is initialized.
+ ///
+ /// # Post-condition
+ ///
+ /// 1. `this.active_fut` is fully initialized
+ pub(crate) unsafe fn open>(this: *mut Self, scope: S)
+ where
+ T: for<'a> Family<'a>,
+ F: Future,
+ S: TopScope,
+ {
+ // SAFETY: precondition (1)
+ let RawScopeFields { state, active_fut } = unsafe { Self::fields(this) };
+
+ let time_capsule = TimeCapsule { state };
+
+ // SAFETY:
+ // - precondition (1)
+ // - using `scope.run` from the executor
+ unsafe {
+ active_fut.write(scope.run(time_capsule));
+ }
+ }
+}
+
+impl RawScope
+where
+ T: for<'a> Family<'a>,
+ F: Future,
+{
+ /// # Safety
+ ///
+ /// 1. `this` points to a properly aligned, fully initialized `RawScope`.
+ /// 2. `this` verifies the guarantees of `Pin` (one of its fields is pinned in this function)
+ /// 3. No other exclusive reference to the frozen value. In particular, no concurrent calls to this function.
+ #[allow(unused_unsafe)]
+ pub(crate) unsafe fn enter<'borrow, Output: 'borrow, G>(this: NonNull, f: G) -> Output
+ where
+ G: for<'a> FnOnce(&'a mut >::Family) -> Output,
+ {
+ // SAFETY: precondition (1)
+ let RawScopeFields { state, active_fut } = unsafe { Self::fields(this.as_ptr()) };
+
+ // SAFETY: precondition (2)
+ let active_fut: Pin<&mut F> = unsafe { Pin::new_unchecked(&mut *active_fut) };
+
+ match active_fut.poll(&mut std::task::Context::from_waker(&waker::create())) {
+ Poll::Ready(never) => match never {},
+ Poll::Pending => {}
+ }
+
+ // SAFETY:
+ // - dereferenceable: precondition (1)
+ // - drop: reading a reference (no drop glue)
+ // - aliasing: precondition (3) + `mut_ref` cannot escape this function via `f`
+ // - lifetime: the value is still live due to the precondition on `Scope::run`,
+ // preventing
+ let mut_ref = unsafe {
+ state
+ .read()
+ .expect("The scope's future did not fill the value")
+ .as_mut()
+ };
+
+ f(mut_ref)
+ }
+}
+
+impl<'a, 'b, T> Future for FrozenFuture<'a, 'b, T>
+where
+ T: for<'c> Family<'c>,
+{
+ type Output = ();
+
+ fn poll(
+ mut self: std::pin::Pin<&mut Self>,
+ _cx: &mut std::task::Context<'_>,
+ ) -> Poll {
+ // SAFETY:
+ // - state was set to a valid value in [`TimeCapsule::freeze`]
+ // - the value is still 'live', due to the lifetime in `FrozenFuture`
+ let state: &mut State = unsafe { &mut *self.state };
+ if state.is_none() {
+ let mut_ref = self
+ .mut_ref
+ .take()
+ .expect("poll called several times on the same future");
+
+ *state = Some(mut_ref);
+ Poll::Pending
+ } else {
+ *state = None;
+ Poll::Ready(())
+ }
+ }
+}
diff --git a/src/scope.rs b/src/scope.rs
index 3229192..1ae5f55 100644
--- a/src/scope.rs
+++ b/src/scope.rs
@@ -1,200 +1,223 @@
-use crate::{waker, Family, Never};
-use std::{
- cell::{Cell, RefCell},
- future::Future,
- marker::PhantomData,
- mem::ManuallyDrop,
- ops::DerefMut,
- pin::Pin,
- task::Poll,
-};
+//! Defines a generic `Scope` as a trait that can be instantiated as a [`crate::BoxScope`].
+use std::{future::Future, marker::PhantomData};
-/// The future resulting from using a time capsule to freeze some scope.
-pub struct FrozenFuture<'a, 'b, T>
-where
- T: for<'c> Family<'c>,
- 'b: 'a,
-{
- mut_ref: Cell>::Family>>,
- state: *const State,
-}
+use crate::{Family, Never, TimeCapsule};
+
+/// Trait sealed for safety.
+///
+/// The trait is only implemented on [`crate::scope::Wrapper`]
+pub(crate) trait Sealed {}
-/// Passed to the closures of a scope so that they can freeze the scope.
-pub struct TimeCapsule
+impl Sealed for Wrapper
where
- T: for<'a> Family<'a>,
+ P: FnOnce(super::TimeCapsule) -> Future,
+ Family: for<'a> crate::Family<'a>,
+ Future: std::future::Future,
{
- state: *const State,
}
-impl TimeCapsule
-where
- T: for<'a> Family<'a>,
-{
- /// Freeze a scope, making the data it has borrowed available to the outside.
- ///
- /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`].
- ///
- /// For simple cases where you don't need to execute code in the scope between two calls to `enter`,
- /// use [`Self::freeze_forever`].
- pub fn freeze<'a, 'b>(
- &'a mut self,
- t: &'a mut >::Family,
- ) -> FrozenFuture<'a, 'b, T>
- where
- 'b: 'a,
- {
- FrozenFuture {
- mut_ref: Cell::new(Some(t)),
- state: self.state,
- }
- }
+/// A scope that can be frozen in time.
+///
+/// To get a `Scope`, use the [`crate::scope!`] macro.
+#[allow(private_bounds)]
+pub trait Scope: Sealed {
+ /// The helper struct that serves to define the reference type.
+ type Family: for<'a> Family<'a>;
+ /// The output type of this scope.
+ type Output;
+ /// The underlying future that serves as a coroutine to freeze the scope.
+ type Future: Future;
- /// Freeze a scope forever, making the data it has borrowed available to the outside.
+ /// Runs a scope by injecting a [`TimeCapsule`].
+ ///
+ /// # Safety
///
- /// Once a scope is frozen, its borrowed data can be accessed through [`crate::BoxScope::enter`].
+ /// - This function is only safe if the produced future is awaited immediately.
///
- /// If you need to execute code between two calls to [`crate::BoxScope::enter`], use [`Self::freeze`].
- pub async fn freeze_forever<'a, 'b>(
- &'a mut self,
- t: &'a mut >::Family,
- ) -> Never {
- loop {
- self.freeze(t).await
- }
- }
+ /// Using the `sub_scope` macro inside a [`crate::scope!`] always verifies this condition and is therefore always safe.
+ unsafe fn run(self, time_capsule: TimeCapsule) -> Self::Future;
}
-struct State(Cell<*mut >::Family>)
-where
- T: for<'a> Family<'a>;
+/// A top-level [`Scope`], always returning [`crate::Never`].
+///
+/// Create one using the [`crate::scope!`] macro.
+pub trait TopScope: Scope {}
-impl Default for State
-where
- T: for<'a> Family<'a>,
-{
- fn default() -> Self {
- Self(Cell::new(std::ptr::null_mut()))
- }
-}
+impl TopScope for S where S: Scope {}
-/// Underlying representation of a scope.
-pub(crate) struct Scope
+/// A wrapper for a producer.
+///
+/// See [`Scope`] for more information.
+struct Wrapper(P, PhantomData<*const Family>)
where
- T: for<'a> Family<'a>,
- F: Future,
-{
- pub(crate) active_fut: RefCell>>,
- phantom: PhantomData<*const fn(TimeCapsule) -> F>,
- state: State,
-}
+ P: FnOnce(TimeCapsule) -> Future,
+ Family: for<'a> crate::Family<'a>,
+ Future: std::future::Future;
-impl Scope
+impl Scope for Wrapper
where
- T: for<'a> Family<'a>,
- F: Future,
+ P: FnOnce(TimeCapsule) -> Future,
+ Family: for<'a> crate::Family<'a>,
+ Future: std::future::Future,
{
- /// Creates a new closed scope.
- pub fn new() -> Self {
- Self {
- active_fut: RefCell::new(None),
- phantom: PhantomData,
- state: Default::default(),
- }
- }
+ type Family = Family;
+ type Output = Output;
+ type Future = Future;
- /// # Safety
- ///
- /// The `this` parameter is *dereference-able*.
- #[allow(unused_unsafe)]
- pub(crate) unsafe fn open(this: std::ptr::NonNull, producer: P)
- where
- P: FnOnce(TimeCapsule) -> F,
- {
- // SAFETY: `this`Â is dereference-able as per precondition.
- let this = unsafe { this.as_ref() };
- let mut active_fut = this.active_fut.borrow_mut();
- if active_fut.is_some() {
- panic!("Multiple calls to open")
- }
- let state: *const State = &this.state;
- let time_capsule = TimeCapsule { state };
- let fut = producer(time_capsule);
- *active_fut = Some(ManuallyDrop::new(fut));
- }
-
- /// # Safety
- ///
- /// The `this` parameter is *dereference-able*.
- #[allow(unused_unsafe)]
- pub(crate) unsafe fn enter<'borrow, Output: 'borrow, G>(
- this: std::ptr::NonNull,
- f: G,
- ) -> Output
- where
- G: for<'a> FnOnce(&'a mut >::Family) -> Output,
- {
- // SAFETY: `this`Â is dereference-able as per precondition.
- let this = unsafe { this.as_ref() };
-
- let mut fut = this.active_fut.borrow_mut();
- let fut = fut.as_mut().unwrap().deref_mut();
- // SAFETY: self.active_fut is never moved by self after the first call to produce completes.
- // self itself is pinned.
- let fut = unsafe { Pin::new_unchecked(fut) };
- // SAFETY: we didn't do anything particular here before calling `poll`, which may panic, so
- // we have nothing to handle.
- match fut.poll(&mut std::task::Context::from_waker(&waker::create())) {
- Poll::Ready(_) => unreachable!(),
- Poll::Pending => {}
- }
- let state = this.state.0.get();
- // SAFETY: cast the lifetime of the Family to `'borrow`.
- // This is safe to do
- let state: *mut ::Family = state.cast();
- let output;
- {
- // SAFETY: The `state` variable has been set to a dereference-able value by the future,
- // or kept its NULL value.
- let state = unsafe {
- state
- .as_mut()
- .expect("The scope's future did not fill the value")
- };
- // SAFETY: we're already in a clean state here even if `f` panics.
- // (not doing anything afterwards beside returning `output`)
- output = f(state);
- }
- output
+ unsafe fn run(self, time_capsule: TimeCapsule) -> Self::Future {
+ (self.0)(time_capsule)
}
}
-impl<'a, 'b, T> Future for FrozenFuture<'a, 'b, T>
+#[doc(hidden)]
+/// Constructs a new scope from a producer
+///
+/// # Safety
+///
+/// - This function is only safe if the producer guarantees that any call to `crate::TimeCapsule::freeze` or
+/// `crate::TimeCapsule::freeze_forever` happens at the top level of the producer,
+/// and that the resulting future is awaited immediately.
+///
+/// Using the [`crate::scope!`] macro always verifies this condition and is therefore always safe.
+pub unsafe fn new_scope(
+ producer: P,
+) -> impl Scope
where
- T: for<'c> Family<'c>,
+ P: FnOnce(TimeCapsule) -> Future,
+ Family: for<'a> crate::Family<'a>,
+ Future: std::future::Future,
{
- type Output = ();
-
- fn poll(
- self: std::pin::Pin<&mut Self>,
- _cx: &mut std::task::Context<'_>,
- ) -> Poll {
- // SAFETY: `state` has been set in the future by the scope
- let state = unsafe { self.state.as_ref().unwrap() };
- if state.0.get().is_null() {
- let mut_ref = self
- .mut_ref
- .take()
- .expect("poll called several times on the same future");
- let mut_ref: *mut ::Family = mut_ref;
- // SAFETY: Will be given back a reasonable lifetime in the `enter` method.
- let mut_ref: *mut >::Family = mut_ref.cast();
+ Wrapper(producer, PhantomData)
+}
- state.0.set(mut_ref);
- Poll::Pending
- } else {
- state.0.set(std::ptr::null_mut());
- Poll::Ready(())
- }
- }
+/// A macro to open a scope that can be frozen in time.
+///
+/// You can write code like you normally would in that scope, but you get 3 additional superpowers:
+///
+/// 1. `freeze!(&mut x)`: interrupts execution of the scope until the next call to [`crate::BoxScope::enter`],
+/// that will resume execution. The passed `&mut x` will be available to the next call to [`crate::BoxScope::enter`].
+/// 2. `freeze_forever!(&mut x)`: interrupts execution of the scope forever.
+/// All future calls to [`crate::BoxScope::enter`] will have access to the passed `&mut x`.
+/// 3. `subscope!(some_subscope(...))`: execute an expression that can be another function returning a `scope!` itself.
+/// This is meant to be able to structure your code in functions.
+///
+/// A `scope!` invocation returns some type that `impl Scope` or `impl TopScope` (when the scope never returns).
+/// The `Family` type of the `Scope` typically needs to be annotated, whereas the `Future` type should not be.
+///
+///
+/// # Using a subscope
+///
+/// subscopes are useful to split your logic into smaller units (like functions),
+/// and in particular for error handling.
+/// ```
+/// use nolife::{BoxScope, Scope, SingleFamily, TopScope, scope};
+///
+///
+/// fn outer_scope(
+/// input_data: Vec,
+/// ) -> impl TopScope>>> {
+/// scope!({
+/// for input in input_data {
+/// match sub_scope!(inner_scope(input)) {
+/// Ok(string) => {
+/// freeze!(&mut Ok(Some(string)));
+/// }
+/// Err(err) => {
+/// freeze_forever!(&mut Err(err));
+/// }
+/// }
+/// }
+/// freeze_forever!(&mut Ok(None))
+/// })
+/// }
+///
+/// fn inner_scope(
+/// mut input: impl std::io::Read,
+/// ) -> impl Scope>>, Output = std::io::Result>
+/// {
+/// scope!({
+/// let mut buf = String::new();
+/// input.read_to_string(&mut buf)?;
+/// freeze!(&mut Ok(Some(buf.clone())));
+/// Ok(buf)
+/// })
+/// }
+/// ```
+/// # Using a scope with a reference in input
+///
+/// ```
+/// use nolife::{SingleFamily, BoxScope, TopScope, scope};
+///
+/// fn scope_with_ref<'scope, 'a: 'scope>(
+/// s: &'a str,
+/// ) -> impl TopScope> + 'scope {
+/// scope!({ freeze_forever!(&mut s.len()) })
+/// }
+/// let x = "Intel the Beagle".to_string();
+/// let mut scope = BoxScope::, _>::new_typed(scope_with_ref(&x));
+///
+/// scope.enter(|x| assert_eq!(*x, 16));
+/// ```
+///
+/// # Panics
+///
+/// The block passed to `scope` is technically an `async` block, but trying to `await` a future in this block
+/// will always result in a panic.
+#[macro_export]
+macro_rules! scope {
+ ($b:block) => {
+ match move |#[allow(unused_variables, unused_mut)] mut time_capsule| async move {
+ 'check_top: {
+ #[allow(unreachable_code)]
+ if false {
+ break 'check_top (loop {});
+ }
+ /// `freeze!(&mut x)` interrupts execution of the scope, making `&mut x` available to the next call
+ /// to [`nolife::BoxScope::enter`].
+ ///
+ /// Execution will resume after a call to [`nolife::BoxScope::enter`].
+ #[allow(unused_macros)]
+ macro_rules! freeze {
+ ($e:expr) => {
+ #[allow(unreachable_code)]
+ if false {
+ break 'check_top (loop {});
+ }
+ $crate::TimeCapsule::freeze(&mut time_capsule, $e).await
+ }
+ }
+ /// `freeze_forever!(&mut x)` stops execution of the scope forever, making `&mut x` available to all future calls
+ /// to [`$crate::BoxScope::enter`].
+ ///
+ /// Execution will never resume.
+ #[allow(unused_macros)]
+ macro_rules! freeze_forever {
+ ($e:expr) => {{
+ #[allow(unreachable_code)]
+ if false {
+ break 'check_top (loop {});
+ }
+ $crate::TimeCapsule::freeze_forever(&mut time_capsule, $e).await}
+ }
+ }
+ /// `sub_scope(some_scope)` runs the sub-scope `some_scope` to completion before continuing execution of the current scope,
+ /// yielding the output value of the sub-scope.
+ ///
+ /// `some_scope` is typically an expression that is itself a `scope!`.
+ ///
+ /// This macro is meant to allow you to structure your code in multiple functions.
+ #[allow(unused_macros)]
+ macro_rules! sub_scope {
+ ($e:expr) => {{
+ #[allow(unreachable_code)]
+ if false {
+ break 'check_top (loop {});
+ }
+ match $e { e => unsafe { $crate::scope::Scope::run(e, time_capsule).await } }
+ }}
+ }
+ $b
+ }
+ } { scope => unsafe { $crate::scope::new_scope(scope) } }
+ };
}