Owning References

I was thinking of an idea of owning references that you can move a value out of and will behave like an owned object except its data is stored elsewhere.

fn main() {
  let thing = ThingStruct;
  if condition {
    f1(&own thing);
  }
  else { 
    f2(&own thing);
  }
  // no dropping thing here - its ownership moved
}

fn f1(thing: &own dyn Thing) {
  thing.do_some_thing(); // fn(&mut Self)
  // drop(thing) here
}

fn f2(thing: &own dyn Thing) {
  let b = own_ref_box!(thing); // some compiler builtin macro that moves the value into a Box
  
  SOME_GLOBAL_STATE.push(b);
}

It is currently not possible to give a function an owned dyn Trait and let the function deal with dropping it, or boxing it and storing elsewhere. You can give a &mut dyn Trait but a &mut reference is expected to have a valid value in it and can't be moved out of.

(yes, i've seen !Sized on stack rfcs, this is a different approach without involving dynamic stack allocation)

This could also be a way to call functions that take an owned Self in dyn traits, except instead of self it will be &own self

And that would allow FnOnce being boxed, though through a trampoline trait that takes a &own Self, moves it out and calls the call_once:

trait FnOwnRef<Args: Tuple> {
    type Output;
    fn call_once_ref(&own self, args: Args) -> Self::Output;
}

impl<T: FnOnce<A>, A> FnOwnRef<A> for T {
    type Output = <Self as FnOnce>::Output;
    fn call_once_ref(&own self, args: Args) -> Self::Output {
        (*self).call_once(args)
    }
}

I believe this to be a special case of Box (specifically, the case in which the allocator allocates into storage borrowed from an existing object). However, it isn't compatible with the current allocator API because that requires allocators to be able to allocate more than once (Allocator::allocate takes &self) – in order to make it work, you instead need an allocator API in which allocation consumes the allocator and returns a separate dealocator object. I've been working on this sort of allocator API for a while but haven't finished the code yet.

It's almost doable even without compiler magic (you use a MaybeUninit as the storage) – the only magical part is "absorbing" the reference back into the object it's borrowed from, converting it back from a MaybeUninit to a known-initialised object. (The problem is that if the wrong Box is used, the value might be stored in the wrong place – this is probably best implemented as moving the value if it's in the wrong place, and attempting to optimise out the move on the basis that it's normally in the right place.) I suspect that for most practical uses, the "absorb" operation isn't even needed.

One problem is that this is dependent on sized-hierarchy changes to make it possible to have an unsized MaybeUninit (because the sizedness needs to be specifically "sized based on fat pointer metadata" rather than "sized based on information in the target of the reference" – the former works with references to MaybeUninit, the latter doesn't). The relevant trait is called MetaSized in the current sized-hierarchy design.

Would the proposed Store API allow this kind of thing?

You would still need to be able to construct that Box, but you can't take a reference to the object, otherwise you must leave it in valid state after reference ends and you can't move it into the Box since that needs to have the allocator know the object size at comptime (and you won't be able to pass it to any function without specifying specific comptime size in their signature).

Unless that was the compiler magic you were talking about

You can just construct it the usual way for a Box – do a conceptual move (that is optimised out) of an object of type T into a Box<T>, then (if you want the box to have an unsized type) unsize the resulting Box. (Making the allocator know the size at compile time is also easy – just give the allocation methods a generic parameter for the type. I was planning to do that anyway – allocator implementations love knowing the size at compile time, ti can make them massively more efficient.)

The alternative of creating the object as a T and doing an in-place transmute into an initialized MaybeUninit<T> would also be viable, but as you suggest would need compiler magic because Rust doesn't currently have in-place transmutes (a strong update would be sufficient).

It's certainly playing around in the same space that I am (and it's one of the sources of inspiration for what I'm attempting). But I'm trying to abstract things at a lower level, where hopefully it's less complicated.

so

let obj = todo!(); // impl Iterator for example
let b = Box::new_in(obj, InPlaceAlloc) as Box<dyn Iterator, _>;
f(b); 

fn f(b: Box<dyn Iterator, InPlaceAlloc>) {}

since the Box is moved into the function here, what happens to the data of obj? Rust can't track when the place of the object can be freed since Box doesn't have a lifetime associated with it Allocator could have a lifetime but i'm not sure rust will understand transforming an object with seemingly no lifetime into an object with a lifetime

Yes, since object is on the stack, it will be automatically "freed" when the function returns, but what will prevent that Box being stored somewhere outside while still pointing to the stack

I think that to do it without compiler magic, using the current allocator API, it has to go like this:

let obj = todo!();
let obj_place = MaybeUninit::new();
let b = Box::new_in(obj, InPlaceAlloc(&mut obj_place));

and now b has a lifetime (because it embeds an InPlaceAlloc object that has a lifetime).

(This is essentially the same problem as the "self-referencing struct" problem, and this code is what you get if you translate the usual solution.)

One downside of this approach is that you don't want the reference that the InPlaceAlloc object wraps to actually be stored in the Box (where it would uselessly take up space), just to be used for the allocation. This is why I prefer a different allocator API in which boxes store a deallocator rather than an allocator (and avoid the need to allow information that's only useful for allocation to be stored in the Box).

A second downside is that obj and obj_place conceptually exist at the same time, making it hard for the compiler to give them both the same memory address (which is how we'd want this to be optimised). It's probably possible to implement that optimisation somehow, but adding compiler magic might be easier at this point.