Creating Our Own Fibers 122 The guard function is called when the function that we passed in, f, has returned. When f returns, it means our task is finished, so we de-reference our Runtime and call t_return(). We could have made a function that does some additional work when a thread is finished, but right now, our t_return() function does all we need. It marks our thread as Available (if it's not our base thread) and yields so we can resume work on a different thread. Next is our skip function: #[naked] unsafe extern "C" fn skip() { asm!("ret", options(noreturn)) } There is not much happening in the skip function. We use the #[naked] attribute so that this function essentially compiles down to just ret instruction. ret will just pop off the next value from the stack and jump to whatever instructions that address points to. In our case, this is the guard function. Next up is a small helper function named yield_thread : pub fn yield_thread() { unsafe { let rt_ptr = RUNTIME as *mut Runtime; (*rt_ptr). t_yield(); }; } This helper function lets us call t_yield on our Runtime from an arbitrary place in our code without needing any references to it. This function is very unsafe, and it's one of the places where we make big shortcuts to make our example slightly simpler to understand. If we call this and our Runtime is not initialized yet or the runtime is dropped, it will result in undefined behavior. However, making this safer is not a priority for us just to get our example up and running. We are very close to the finish line; just one more function to go. The last bit we need is our switch function, and you already know the most important parts of it already. Let's see how it looks and explain how it differs from our first stack swap function: #[naked] #[no_mangle] unsafe extern "C" fn switch() { asm!( "mov [rdi + 0x00], rsp", "mov [rdi + 0x08], r15", "mov [rdi + 0x10], r14", "mov [rdi + 0x18], r13", "mov [rdi + 0x20], r12", "mov [rdi + 0x28], rbx", "mov [rdi + 0x30], rbp",