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