3 ms·
Then you haven't ever written a line of C/C++ I presume? Ownership is just a formalization of the way almost all resources (including memory) are managed in C:
by ben-schaaf 6y ago
Then you haven't ever written a line of C/C++ I presume? Ownership is just a formalization of the way almost all resources (including memory) are managed in C: A single function has the responsibility of doing cleanup; that responsibility can be moved around to other functions of course, but only one ever has that responsibility at a time.
- mikewarot 6y ago>Then you haven't ever written a line of C/C++ I presume? Correct... I'm a Pascal programmer. I create objects when a program starts, delete them at the end. I've never really had any of the pointer problems that seem to plague C/C++.
- wwright 6y agoI assume you write fairly "static" software that knows its job pretty well in advance. Ada programs tend to use a similar allocation strategy. A lot of programs written in C and C++ are very dynamic, and may need to massively increase or decrease the number of objects they use while running. For example, consider an audio workstation: when you first start it, it has very little to do, but the user can add literally hundreds or thousands of tracks, each with dozens of effects and modifications. Each of these features is composed of many objects. Ownership comes in when these objects have complex relationships, where one object may need to be referenced by many others. When it is time to clean up some of these objects — for example, when a user deletes a track in the audio workstation — you have to know which of these shared objects to delete. Ownership is one way of modelling that problem. Languages that talk about ownership try to optimize the "best case" where an object has exactly one owner, because then it's very simple for the compiler to free that object. The trick is that you have to prove that no one else is using it, or else you have memory errors.
- pwdisswordfish4 6y ago> Correct... I'm a Pascal programmer. I create objects when a program starts, delete them at the end. Then I guess you should instead say you’re a ‘single-shot, compiler-like batch job executable’ programmer. I imagine that colours your perception more than the choice of programming language itself. If you were to write a program that has to manipulate deeply nested data structures, or a long-running service that has to acquire and dispose of an unbounded number of various kinds of resources over the course of its runtime in a deterministic manner, you’d start longing for an ownership system very quickly.
- mikewarot 6y agoI'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?
- steveklabnik 6y agoIt 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."
- mikewarot 6y agoThat 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?
- steveklabnik 6y agoNo 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.
- randomNumber7 6y ago> Correct... I'm a Pascal programmer. Real men don't use pascal ;) Have you ever closed a file?