Owning pointers: could they exist/ be helpful?

I just had an idea for an owning pointer in an ownership/borrow system. Would it be possible to have something like &own T in Rust or any future language that is based on ownership and borrowing. This could express ownership without having to move the value. My thought was that it might could be an alternative to Box but doesn't have to be allocated on the heap.

How is this different than &mut T? You couldn't re borrow T. When it's lifetime ends, it drops T. You could also move T out of the reference ending the reference's lifetime.

Why would this be helpful? That is kind of my question. But ideas I had:

  • LinkedList without the heap. These may not be that helpful. For example, I don't think you could push items onto it in a loop. Also maybe a bad api: list.push(&own Link::new(2)).
  • Not having to move large items. &mut T might already solved 99% of that use case.
  • Make self referring types easier?
  • replace Box. &own T would then need to know if it is allocated on the heap or not so it can clean up memory properly. What would the lifetime be? 'static? Would Rust need a Move trait to make this work because you would have to de allocate when you move T out of the reference?

Do y'all think this would make sense with ownership and borrowing? Do you think it would be useful?

If if is going to store it on the stack, it must have a lifetime to ensure it can't exist after that stack frame returns. If that's what you want, you can already make it yourself by internally storing an &mut Option<T>, which would allow you to move the stored value out.

It would not make linked lists nor self-referential types easier than they are now.

2 Likes

Yeah I guess it doesn't make sense. I just implemented the stack based linked list using &mut. The only improvement I think &own would have is making it easier to use non-copy types in the list.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9fbafba0c17335de91c7bec1fbb1ef19

I guess you could do this kind of thing with an enum.

enum OwningPointer<'a, T> {
    Heap(Box<T>),
    Stack(&'a mut Option<T>)
}

'a would be 'static for Heap. You could have a move method:

fn move(self) -> T {
    //...
}

and implement Deref and DerefMut.

The basic answer is that "using indirection to save copies" is an optimisation / implementation detail that the language can do, but that the high level language does not necessarily have to express. That is, implementation-wise, a mem::drop::<BigThing> and ManuallyDrop::<BigThing>::drop are very likely to end up being equivalent / having the same assembly. But the high level semantics of these two functions are quite different (one is unsafe when the other isn't, for instance!).

That being said, starting from that observation, one can implement a proof-of-concept of your idea, either by using a macro, or by using a callback (required by stack shenanigans).

And this proof-of-concept could actually become useful, when indirection becomes more than just an implementation optimisation: with indirection, we can have trait objects! So using such an Own<'_, T> owning (stack-allocated) pointer could mean that we could have owned trait objects without heap allocation :slightly_smiling_face:

I don't see how owned things on stack are actually usable as owned. Everything that is on stack is bounded by stack frame's lifetime.

You can have a trait object without heap allocation:

let a = 1;
let b: &dyn Debug = &a;

and trait object is a (data pointer + vtable pointer), so I guess that in-memory representation of an "owned" on-stack trait object would still be identical to this.

1 Like

The only difference I can see between your proposal and Box is that yours doesn't allocate on the heap, but the fact that Box uses the heap is largely an implementation detail and not something expressly exposed by Box's API. The optimizer could also do escape analysis in order to allocate boxes on the stack, or even in registers.

Nothing - new - under - the - sun.

There's more on internals.rust-lang.org, in fact this topic is better suited to internals. I would love to revive conversation about &own/&move

4 Likes

For me ownership relates more to (the possibility) of dropping something rather than just it being 'static. Or in other terms, a type X owns a T if there exists a X -> T transformation:

  • T trivially owns T;

  • &'_ mut Option<T> owns T by virtue of Option::take;

  • And a generalization of the previous one, my prototyped, Own<'_, T> also owns the T it points to (granted, I don't explicitly provide the Own<'_, T> -> T extractor, but it would be as simple as:

    impl<'frame, T : 'lt> Own<'frame, T> {
        pub
        fn into_inner (self: Own<'frame, T>)
          -> T
        {
            unsafe { ManuallyDrop::take(&mut *ManuallyDrop::new(self).0) }
        }
    }
    

An example where having that could be useful would be to avoid having to Option-wrap an FnOnce in order to make it (a fake) FnMut so that it can be dispatched from &mut and thus from the stack.

Basically, I'd expect Rust to, at some point, support the following (optionally in a language-sugared fashion):

fn mk_closure_1 ()
  -> impl FnOnce()
{
    let on_drop = ::scopeguard::guard((), |()| println!("Dropped env 1"));
    move || {
        let _ = on_drop; // captured
        println!("Called 1")
    }
}

fn mk_closure_2 ()
  -> impl FnOnce()
{
    let on_drop = ::scopeguard::guard((), |()| println!("Dropped env 2"));
    move || {
        let _ = on_drop; // captured
        println!("Called 2")
    }
}

fn main ()
{
    let (mut obj1, mut obj2);
    let obj: Own<'_, dyn FnOnceStack<(), /* -> */ ()> =
        if ::rand::random() {
            obj1 = ManuallyDrop::new(mk_closure_1());
            unsafe { Own::new(&mut obj1) }.into_dyn_FnOnce()
        } else {
            obj2 = ManuallyDrop::new(mk_closure_2());
            unsafe { Own::new(&mut obj2) }.into_dyn_FnOnce()
        }
    ;
    if ::rand::random::<u8>() > 32 {
        obj();
    } else {
        drop(obj);
    }
    // drop(obj); /* Would error */
}

See https://crates.io/crates/refmove

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.