Struct Context

Source
#[repr(C)]
pub struct Context(/* private fields */);
Expand description

A Context stores a ContextFn’s state of execution, for it to be resumed later.

If we have 2 or more Context instances, we can thus easily “freeze” the current state of execution and explicitely switch to another Context. This Context is then resumed exactly where it left of and can in turn “freeze” and switch to another Context.

§Examples

See examples/basic.rs

Implementations§

Source§

impl Context

Source

pub unsafe fn new(stack: &Stack, f: ContextFn) -> Context

Creates a new Context prepared to execute f at the beginning of stack.

f is not executed until the first call to resume().

It is unsafe because it only takes a reference of Stack. You have to make sure the Stack lives longer than the generated Context.

Examples found in repository?
examples/basic.rs (line 31)
14fn main() {
15    // This method will always `resume()` immediately back to the
16    // previous `Context` with a `data` value incremented by one starting at 0.
17    // You could thus describe this method as a "natural number generator".
18    extern "C" fn context_function(mut t: Transfer) -> ! {
19        for i in 0usize.. {
20            print!("Yielding {} => ", i);
21            t = unsafe { t.context.resume(i) };
22        }
23
24        unreachable!();
25    }
26
27    // Allocate some stack.
28    let stack = ProtectedFixedSizeStack::default();
29
30    // Allocate a Context on the stack.
31    let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0);
32
33    // Yield 10 times to `context_function()`.
34    for _ in 0..10 {
35        // Yield to the "frozen" state of `context_function()`.
36        // The `data` value is not used in this example and is left at 0.
37        // The first and every other call will return references to the actual `Context` data.
38        print!("Resuming => ");
39        t = unsafe { t.context.resume(0) };
40
41        println!("Got {}", t.data);
42    }
43
44    println!("Finished!");
45}
More examples
Hide additional examples
examples/fibonacci.rs (line 36)
14fn main() {
15    // This method will always `resume()` immediately back to the
16    // previous `Context` with a `data` value of the next number in the fibonacci sequence.
17    // You could thus describe this method as a "fibonacci sequence generator".
18    extern "C" fn context_function(mut t: Transfer) -> ! {
19        let mut a = 0usize;
20        let mut b = 1usize;
21
22        loop {
23            print!("Yielding {} => ", a);
24            t = unsafe { t.context.resume(a) };
25
26            let next = a + b;
27            a = b;
28            b = next;
29        }
30    }
31
32    // Allocate some stack.
33    let stack = ProtectedFixedSizeStack::default();
34
35    // Allocate a Context on the stack.
36    let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0);
37
38    // Yield 10 times to `context_function()`.
39    for _ in 0..10 {
40        // Yield to the "frozen" state of `context_function()`.
41        // The `data` value is not used in this example and is left at 0.
42        // The first and every other call will return references to the actual `Context` data.
43        print!("Resuming => ");
44        t = unsafe { t.context.resume(0) };
45
46        println!("Got {}", t.data);
47    }
48
49    println!("Finished!");
50}
examples/how_to_ontop.rs (line 135)
129pub fn run() {
130    // Allocate some stack.
131    let mut some_stack = Some(ProtectedFixedSizeStack::default());
132    let stack_ref = stack_ref_from_some_stack(&mut some_stack);
133
134    // Allocate a Context on the stack.
135    let mut ctx = unsafe { Context::new(some_stack.as_ref().unwrap(), context_function) };
136
137    // Yield to context_function(). This important since the returned `Context` reference is
138    // different than the one returned by `Context::new()` (since it points to the entry function).
139    // It's important that we do this first or else calling `Context::resume_ontop()` will crash.
140    // See documentation of `Context::resume_ontop()` for more information.
141    // Furthermore we pass a reference to the Option<ProtectedFixedSizeStack> along with it
142    // so it can delete it's own stack (which is important for stackful coroutines).
143    let Transfer { context, data } = unsafe { ctx.resume(stack_ref) };
144    ctx = context;
145
146    // Store the pointer to the Carrier for `unwind_stack`.
147    let carrier_ptr = data;
148
149    // Yield 10 times to `context_function()`.
150    for _ in 0..10 {
151        // Yield to the "frozen" state of `context_function()`.
152        // The `data` value is not used in this example and is left at 0.
153        print!("Resuming => ");
154        let Transfer { context, data } = unsafe { ctx.resume(0) };
155        ctx = context;
156
157        println!("Got {}", data);
158    }
159
160    // Resume `context_function()` with the ontop function `unwind_stack()`.
161    // Before it returns from it's own call to `resume()` it will call `unwind_stack()`.
162    println!("Resuming context with unwind_stack() ontop!");
163    unsafe { ctx.resume_ontop(carrier_ptr, unwind_stack) };
164
165    match some_stack {
166        Some(..) => println!("Stack is still there (this should not happen here)!"),
167        None => println!("Stack has been deleted!"),
168    }
169
170    println!("Finished!");
171}
Source

pub unsafe fn resume(self, data: usize) -> Transfer

Yields the execution to another Context.

The exact behaviour of this method is implementation defined, but the general mechanism is: The current state of execution is preserved somewhere and the previously saved state in the Context pointed to by self is restored and executed next.

This behaviour is similiar in spirit to regular function calls with the difference that the call to resume() only returns when someone resumes the caller in turn.

The returned Transfer struct contains the previously active Context and the data argument used to resume the current one.

It is unsafe because it is your responsibility to make sure that all data that constructed in this context have to be dropped properly when the last context is dropped.

Examples found in repository?
examples/basic.rs (line 21)
14fn main() {
15    // This method will always `resume()` immediately back to the
16    // previous `Context` with a `data` value incremented by one starting at 0.
17    // You could thus describe this method as a "natural number generator".
18    extern "C" fn context_function(mut t: Transfer) -> ! {
19        for i in 0usize.. {
20            print!("Yielding {} => ", i);
21            t = unsafe { t.context.resume(i) };
22        }
23
24        unreachable!();
25    }
26
27    // Allocate some stack.
28    let stack = ProtectedFixedSizeStack::default();
29
30    // Allocate a Context on the stack.
31    let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0);
32
33    // Yield 10 times to `context_function()`.
34    for _ in 0..10 {
35        // Yield to the "frozen" state of `context_function()`.
36        // The `data` value is not used in this example and is left at 0.
37        // The first and every other call will return references to the actual `Context` data.
38        print!("Resuming => ");
39        t = unsafe { t.context.resume(0) };
40
41        println!("Got {}", t.data);
42    }
43
44    println!("Finished!");
45}
More examples
Hide additional examples
examples/fibonacci.rs (line 24)
14fn main() {
15    // This method will always `resume()` immediately back to the
16    // previous `Context` with a `data` value of the next number in the fibonacci sequence.
17    // You could thus describe this method as a "fibonacci sequence generator".
18    extern "C" fn context_function(mut t: Transfer) -> ! {
19        let mut a = 0usize;
20        let mut b = 1usize;
21
22        loop {
23            print!("Yielding {} => ", a);
24            t = unsafe { t.context.resume(a) };
25
26            let next = a + b;
27            a = b;
28            b = next;
29        }
30    }
31
32    // Allocate some stack.
33    let stack = ProtectedFixedSizeStack::default();
34
35    // Allocate a Context on the stack.
36    let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0);
37
38    // Yield 10 times to `context_function()`.
39    for _ in 0..10 {
40        // Yield to the "frozen" state of `context_function()`.
41        // The `data` value is not used in this example and is left at 0.
42        // The first and every other call will return references to the actual `Context` data.
43        print!("Resuming => ");
44        t = unsafe { t.context.resume(0) };
45
46        println!("Got {}", t.data);
47    }
48
49    println!("Finished!");
50}
examples/how_to_ontop.rs (line 100)
76extern "C" fn context_function(t: Transfer) -> ! {
77    println!("Entering context_function...");
78
79    // Take over the stack from the main function, because we want to manage it ourselves.
80    // The main function could safely return after this in theory.
81    let mut some_stack = take_some_stack_from_transfer(&t);
82    let stack_ref = stack_ref_from_some_stack(&mut some_stack);
83
84    let (result, context) = {
85        let mut carrier = Carrier { context: Some(t.context) };
86
87        let carrier_ptr = &mut carrier as *mut _ as usize;
88
89        // Use `std::panic::catch_unwind()` to catch panics from `unwind_stack()`.
90        let r = panic::catch_unwind(|| {
91            // We use an instance of `Dropper` to demonstrate
92            // that the stack is actually being unwound.
93            let _dropper = Dropper;
94
95            let carrier = unsafe { &mut *(carrier_ptr as *mut Carrier) };
96
97            // We've set everything up! Go back to `main()`!
98            println!("Everything's set up!");
99            let context = carrier.context.take().unwrap();
100            let Transfer { context, .. } = unsafe { context.resume(carrier_ptr) };
101            carrier.context = Some(context);
102
103            for i in 0usize.. {
104                print!("Yielding {} => ", i);
105                let context = carrier.context.take().unwrap();
106                let Transfer { context, .. } = unsafe { context.resume(i) };
107                carrier.context = Some(context);
108            }
109        });
110
111        (r, carrier.context.take().unwrap())
112    };
113
114    match result {
115        Ok(..) => println!("Finished loop without panicking (this should not happen here)!"),
116        Err(..) => println!("Recovered from a panic!"),
117    }
118
119    // We own the stack (`main()` gave it to us) and we need to delete it.
120    // Since it would be unsafe to do so while we're still in the context function running on
121    // that particular stack, we defer deletion of it by resuming `main()` and running the ontop
122    // function `delete_stack()` before `main()` returns from it's call to `resume_ontop()`.
123    println!("Defer stack deallocation by returning to main()!");
124    unsafe { context.resume_ontop(stack_ref, delete_stack) };
125
126    unreachable!();
127}
128
129pub fn run() {
130    // Allocate some stack.
131    let mut some_stack = Some(ProtectedFixedSizeStack::default());
132    let stack_ref = stack_ref_from_some_stack(&mut some_stack);
133
134    // Allocate a Context on the stack.
135    let mut ctx = unsafe { Context::new(some_stack.as_ref().unwrap(), context_function) };
136
137    // Yield to context_function(). This important since the returned `Context` reference is
138    // different than the one returned by `Context::new()` (since it points to the entry function).
139    // It's important that we do this first or else calling `Context::resume_ontop()` will crash.
140    // See documentation of `Context::resume_ontop()` for more information.
141    // Furthermore we pass a reference to the Option<ProtectedFixedSizeStack> along with it
142    // so it can delete it's own stack (which is important for stackful coroutines).
143    let Transfer { context, data } = unsafe { ctx.resume(stack_ref) };
144    ctx = context;
145
146    // Store the pointer to the Carrier for `unwind_stack`.
147    let carrier_ptr = data;
148
149    // Yield 10 times to `context_function()`.
150    for _ in 0..10 {
151        // Yield to the "frozen" state of `context_function()`.
152        // The `data` value is not used in this example and is left at 0.
153        print!("Resuming => ");
154        let Transfer { context, data } = unsafe { ctx.resume(0) };
155        ctx = context;
156
157        println!("Got {}", data);
158    }
159
160    // Resume `context_function()` with the ontop function `unwind_stack()`.
161    // Before it returns from it's own call to `resume()` it will call `unwind_stack()`.
162    println!("Resuming context with unwind_stack() ontop!");
163    unsafe { ctx.resume_ontop(carrier_ptr, unwind_stack) };
164
165    match some_stack {
166        Some(..) => println!("Stack is still there (this should not happen here)!"),
167        None => println!("Stack has been deleted!"),
168    }
169
170    println!("Finished!");
171}
Source

pub unsafe fn resume_ontop(self, data: usize, f: ResumeOntopFn) -> Transfer

Yields the execution to another Context and executes a function “ontop” of it’s stack.

This method identical to resume() with a minor difference:

The argument f is executed right after the targeted Context, pointed to by self, is woken up, but before it returns from it’s call to resume(). f now gets passed the Transfer struct which would normally be returned by resume() and is allowed to inspect and modify it. The Transfer struct f returns is then finally the one returned by resume() in the targeted Context.

This behaviour can be used to either execute additional code or map the Transfer struct to another one before it’s returned, without the targeted Context giving it’s consent. For instance it can be used to unwind the stack of an unfinished Context, by calling this method with a function that panics, or to deallocate the own stack, by deferring the actual deallocation until we jumped to another, safe Context.

It is unsafe because it is your responsibility to make sure that all data that constructed in this context have to be dropped properly when the last context is dropped.

Examples found in repository?
examples/how_to_ontop.rs (line 124)
76extern "C" fn context_function(t: Transfer) -> ! {
77    println!("Entering context_function...");
78
79    // Take over the stack from the main function, because we want to manage it ourselves.
80    // The main function could safely return after this in theory.
81    let mut some_stack = take_some_stack_from_transfer(&t);
82    let stack_ref = stack_ref_from_some_stack(&mut some_stack);
83
84    let (result, context) = {
85        let mut carrier = Carrier { context: Some(t.context) };
86
87        let carrier_ptr = &mut carrier as *mut _ as usize;
88
89        // Use `std::panic::catch_unwind()` to catch panics from `unwind_stack()`.
90        let r = panic::catch_unwind(|| {
91            // We use an instance of `Dropper` to demonstrate
92            // that the stack is actually being unwound.
93            let _dropper = Dropper;
94
95            let carrier = unsafe { &mut *(carrier_ptr as *mut Carrier) };
96
97            // We've set everything up! Go back to `main()`!
98            println!("Everything's set up!");
99            let context = carrier.context.take().unwrap();
100            let Transfer { context, .. } = unsafe { context.resume(carrier_ptr) };
101            carrier.context = Some(context);
102
103            for i in 0usize.. {
104                print!("Yielding {} => ", i);
105                let context = carrier.context.take().unwrap();
106                let Transfer { context, .. } = unsafe { context.resume(i) };
107                carrier.context = Some(context);
108            }
109        });
110
111        (r, carrier.context.take().unwrap())
112    };
113
114    match result {
115        Ok(..) => println!("Finished loop without panicking (this should not happen here)!"),
116        Err(..) => println!("Recovered from a panic!"),
117    }
118
119    // We own the stack (`main()` gave it to us) and we need to delete it.
120    // Since it would be unsafe to do so while we're still in the context function running on
121    // that particular stack, we defer deletion of it by resuming `main()` and running the ontop
122    // function `delete_stack()` before `main()` returns from it's call to `resume_ontop()`.
123    println!("Defer stack deallocation by returning to main()!");
124    unsafe { context.resume_ontop(stack_ref, delete_stack) };
125
126    unreachable!();
127}
128
129pub fn run() {
130    // Allocate some stack.
131    let mut some_stack = Some(ProtectedFixedSizeStack::default());
132    let stack_ref = stack_ref_from_some_stack(&mut some_stack);
133
134    // Allocate a Context on the stack.
135    let mut ctx = unsafe { Context::new(some_stack.as_ref().unwrap(), context_function) };
136
137    // Yield to context_function(). This important since the returned `Context` reference is
138    // different than the one returned by `Context::new()` (since it points to the entry function).
139    // It's important that we do this first or else calling `Context::resume_ontop()` will crash.
140    // See documentation of `Context::resume_ontop()` for more information.
141    // Furthermore we pass a reference to the Option<ProtectedFixedSizeStack> along with it
142    // so it can delete it's own stack (which is important for stackful coroutines).
143    let Transfer { context, data } = unsafe { ctx.resume(stack_ref) };
144    ctx = context;
145
146    // Store the pointer to the Carrier for `unwind_stack`.
147    let carrier_ptr = data;
148
149    // Yield 10 times to `context_function()`.
150    for _ in 0..10 {
151        // Yield to the "frozen" state of `context_function()`.
152        // The `data` value is not used in this example and is left at 0.
153        print!("Resuming => ");
154        let Transfer { context, data } = unsafe { ctx.resume(0) };
155        ctx = context;
156
157        println!("Got {}", data);
158    }
159
160    // Resume `context_function()` with the ontop function `unwind_stack()`.
161    // Before it returns from it's own call to `resume()` it will call `unwind_stack()`.
162    println!("Resuming context with unwind_stack() ontop!");
163    unsafe { ctx.resume_ontop(carrier_ptr, unwind_stack) };
164
165    match some_stack {
166        Some(..) => println!("Stack is still there (this should not happen here)!"),
167        None => println!("Stack has been deleted!"),
168    }
169
170    println!("Finished!");
171}

Trait Implementations§

Source§

impl Debug for Context

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.