Creating Our Own Fibers 120 let size = available. stack. len(); unsafe { let s_ptr = available. stack. as_mut_ptr(). offset(size as isize); let s_ptr = (s_ptr as usize & !15) as *mut u8; std::ptr::write(s_ptr. offset(-16) as *mut u64, guard as u64); std::ptr::write(s_ptr. offset(-24) as *mut u64, skip as u64); std::ptr::write(s_ptr. offset(-32) as *mut u64, f as u64); available. ctx. rsp = s_ptr. offset(-32) as u64; } available. state = State::Ready; } } // We close the `impl Runtime` block here Note I promised to point out where we close the impl Runtime block, and we do that after the spawn function. The upcoming functions are “free” functions that don't belong to a struct. While I think t_yield is the logically interesting function in this example, I think spawn is the most interesting one technically. The first thing to note is that the function takes one argument: f: fn(). This is simply a function pointer to the function we take as an argument. This function is the task we want to run concurrently with other tasks. If this was a library, this is the function that users actually pass to us and want our runtime to handle concurrently. In this example, we take a simple function as an argument, but if we modify the code slightly we can also accept a closure. Tip In example ch05/d-fibers-closure, you can see a slightly modified example that accepts a closure instead, making it more flexible than the one we walk through here. I would really encourage you to check that one out once you've finished this example. The rest of the function is where we set up our stack as we discussed in the previous chapter and make sure our stack looks like the one specified in the System V ABI stack layout.