Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm trying to imagine when I'd worry about ownership. The only thing that comes to mind for me is having to provide buffers for string results from OS calls, etc. In Pascal, you can return a string, assign it, append it, etc.. and never have to allocate/dispose of them... its taken care of under the hood by automatic reference counting.

I'd really like to understand this... what specific type of thing were you doing when you found yourself longing for an ownership system?

[edit] It occurs to me that anytime I have objects, they eventually trace their ownership to a single global variable. Perhaps that has something to do with this?



The examples are numerous but for example databases or GUI frameworks. You end up in situations where a parent needs a reference to a child and child needs a reference to its parent and it gets unwieldy.


It is an alternative, in many cases, to automatic reference counting. It's sort of like saying "why would I need automatic reference counting? I have a tracing garbage collector."


That makes sense... thanks!

So, in general, if something is too big to fit to easily be copied, or stored in a single variable of known size (like strings, dictionaries, lists, trees, etc.) its either GC or ARC to the rescue?


No problem. :)

That's not quite it. It's more about compile time vs runtime. Arc and GC are runtime tracking of "when can this be freed." Ownership is compile-time tracking of the same. Rust has refcounted types in its standard library, but you only need to use them relatively rarely. Here's a small example with not too much syntax. The core idea:

    fn main() {
        // String is a heap allocated, mutate-able string type
        let s = String::from("foo");
        
    } // s goes out of scope here, and so will be freed here;
      // the compiler generates the code to free the heap allocation at this point
s is the "owner," and there's only one, so this can be tracked fully at compile time.

Onwership can move, too. Let's introduce a function:

    fn foo(bar: String) {
        // body would go here, elided to keep this simple
        
    } // because bar is of type "String", this function "takes ownership" of the
      // value passed as 's', and so the compiler will generate the code to free
      // its heap allocation at this point

    fn main() {
        let s = String::from("foo");
        
        // we pass s to foo...
        foo(s);
        
    } // ... and because we did, it no longer exists in main, and the compiler
      // knows this, so no code to free the allocation is generated here. if
      // it were, this would lead to use-after-free.
All of this is trackable, 100%, at compile time. So there's no runtime overhead here at all.

But imagine that foo() creates a new thread inside of it, and we want to access said string from inside that thread, as well as from within main. Now we have multiple owners, not a single one, and it's not possible at compile time to know when the string should be freed. The simplest solution here is to reach for reference counting, and you'd end up with Arc<String>, that is, a String that's wrapped in an atomic reference count.

(Also, references don't take ownership, so if you didn't want to have foo free its argument, you'd have it accept a reference, rather than a String directly. This would let you temporarily access the string, without freeing the backing storage when the function's body has run its course.


Wow... that's quite a bit more than Garbage collection... thanks for explaining it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: