API design: whether to use Option or moving self



I have a simple bare struct with a simple interface:

pub struct Context(*const ());

impl Context {
    pub fn empty() -> Context { Context(ptr::null()) }

    pub unsafe fn new(stack_base: *mut (), stack_size: usize, func: extern fn(isize, usize) -> !, param: usize) -> Context {
        // impl omitted

    pub fn is_empty(&self) -> bool { self.0 == ptr::null() }

    pub fn jump(&mut self, store: &mut Context, message: isize) -> isize {
        // impl omitted

    pub fn jump_into(&mut self, message: isize) -> ! {
        // impl omitted

The semantics for jump* functions tell that self should be non-empty and will be nullified during call. I.e. Context will be consumed. Also, store parameter to jump cannot be returned as return value, should be nullptr when used as parameter, and will be initialized by func.
The whole Context is designed as stale storage cell, which doesn’t change its location and is often modified from initialized to empty and back.

My question is, whether this API design is mostly Ok. I see three alternatives:

  1. Leave as-is
  2. Impl for Option, and declare nullability this way
  3. Replace self references with owned values. The problem is, it will clutter user code heavily with things like mem::replace, because storage itself will reside in the same piece of memory all the time.



I’ll reply to myself.

I decided to make mentioned Context type non-nullable. This reduced its public layer to effectively 3 methods (new, jump, jump_into), at the cost of introducing 2 free helper methods jump and jump_into which interact with &mut Option<Context> and hide some operations like mem::replace.