In languages like C you allocate memory yourself, and then you have to make sure to free it up too. This is error prone.
In garbage collected languages like Java there is a garbage collector who figures out when is a piece of memory no longer in use. This has run-time cost.
The third possible way is to make the ownership of that part of the memory explicit. This way the compiler can check memory safety at compile time.
There is a much better description of this in the Rust book: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.htm...
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++.
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.
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.
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?
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?
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.
Real men don't use pascal ;)
Have you ever closed a file?
In Python, you might have a `list` that you built up and them pass its data to multiple functions. You assume these functions are do not mutate their arguments and you keep passing the same `list` over again. In this case, the caller is controlling the invariants of the `list` and therefore "Owns" it.
Deep down, one of those functions needs to mutate a `list` to do its calculations. Where the mutation is happening, it assumes it is the "Owner" and can modify the `list` without violating any invariants. Unfortunately, a "Borrowed" list was passed in and now the caller's invariants are violated.
Rust's ownership model helps to catch this kind of problem at compile time. By default, everything is single-owner though you can change that with a `Mutex`.
You worry about ownership if you have a resource and need to think of when to free/dispose/close it.
You worry about ownership if you have an object that many have a reference to, and some of the references shouldn't keep the associated resources alive, and some other of the references should keep it alive. Those that keep things they referenced alive are (shared) owners.
Scope-based memory management (C++) has copying overhead and destruction overhead, and a whole lot of complexity to avoid those.
Ownership-based memory management removes most of the overhead associated with copying and destruction in a less-complex fashion.
Also not every programming problem in the world is a memory-constrained hard real time problem - in fact, I would say almost all of them are not.
Memory ownership is certainly an interesting thing to have in your arsenal, but a whole class of problems (especially anything with a graph or modestly complex interlinked data structure) can't easily be reasoned about using this model, or you end up copying stuff anyway to get around the borrow checker.
Having said all that, I'd really like to see a language that lets you use a garbage collector 99% of the time for ease of use, but allows you to use other models for the small sections of the code which need it or within certain designated real time threads.