And an eli5 https://www.reddit.com/r/rust/comments/6brtsu/eli5_nonlexica...
And an eli5 https://www.reddit.com/r/rust/comments/6brtsu/eli5_nonlexica...
Variables exist in memory, right? But we only need them for some amount of time - then we want our memory back. There are many strategies for this - Java uses a garbage collector to search through memory and reclaim parts no longer in use, C programmers manually call 'free'. Java suffers from its strategy because of the performance impact of that scan/ freeing strategy. C suffers from the complexity of manually managing memory leading to many security vulnerabilities.
Rust takes this concept of how long memory should live - when it should be freed - and encodes it into its type system. Similar to how you can say 'this thing is an int and it can be added to other ints' rust says 'this thing lives this long and it can reference other things that live this long, or shorter'.
Then the compiler can tell you "hey, you're trying to access something that might have been freed" the same way it would say "you're trying to add an integer to a string".
This means you don't have the performance impact of the GC but you also have a compiler watching your back, telling you when you may be doing something unsafe.
The algorithm used to determine how long variables live and when they can / can't be accessed is the subject of the RFC.
I really like the section of "Thinking in Scopes" in the Book that describes this:
https://doc.rust-lang.org/book/first-edition/lifetimes.html#...
You can think of lifetimes as hidden variables defined by the compiler that are in a sense bound to some block of code. I find this to be the best way to think of them personally.
should be '... or longer '.
The reference only remains valid if the referent hasn't been dropped in the meantime, so it can only reference things that outlive it.
So, if you borrow something and try to store it somewhere with a lifetime the compiler thinks is going to outlive the lifetime of the thing you borrowed, that's a compiler error, whereas in a reference counted language or a GCed language the original entity just keeps existing for as long as required.
This is usually because said entity is on the heap - Rust puts everything on the stack by default.
You can of course put things on the heap, and there is a selection of types available in the standard library which are I guess a bit like C++ smart pointers that let you have heap allocation, reference-counted semantics etc. if you want it.
a = {}
b = {}
l = []
for _ in range(100):
if input('thingy'):
l.append(a)
else:
l.append(b)
In that snippet it's impossible to know how many references there are to a or b.In Rust, lifetime management is set up so that your code can statically determine when something no longer can be referred to (hence "free GC").
For example:
def f():
a = 3
g(a)
# a is no longer used from this point
return 5
you can clearly define when things are no longer usable, hence free-able. Python in this context will also free at the end of the function, but it's because it will be decrementing the reference counter and it gets to 0. But Rust won't need to have the reference counter since it knows it can free the object at any point after g.(This isn't a great example for a lot of reasons, Rust allows you to go much further than this)
This ends up being somewhat similar to writing code in more statically typed languages/languages with dependent types. You might have to rework your code so that the machine can properly identify when it can free memory.
This is because reference counting is prescriptive, that is, the count determines how long a value stays alive. Lifetimes are descriptive, that is, you cannot magically make something live longer by taking an additional reference to it, instead, you'll get an error.
It's not really like that - with reference counting you sort of have multiple 'owners' of a variable, and they all get to 'free' that memory except that 'free' just decrements a counter.
With rust there is a very clear owner at all points - you always know that your code in one spot owns the memory, and no other code does. There may be references handed out but those are very clearly distinct - that is, by looking at the code you can see "yes, I own this" or "yes, I'm borrowing this".
Now, with borrowing it's a little bit more like reference counting, but it's really just scope based. We know that the references are valid for some scope, and after that scope they are not. There's no internal counting or anything like that in the compiler as far as I am aware.
Actually there is another way which you'll find commonly in well written robust libraries - make the argument a const pointer, so that the callee is obviously not responsible for clearing it up and then the callee takes a copy to keep. This also allows passing both const and non const data to the same function, and means that there is much less likely to be an ownership screq-up across a library boundary. As a pattern though it can result in a lot of unnecessary copies, which Rust's system allows you to avoid.
So there is a formal sense of "ownership" of memory in rust? And right now it's based on the lexical scope in which the memory was allocated?
The language generally does specify that they are, and that's exactly how we teach borrowing today. This new one is not more specific, but more general: it allows all programs today to stay the same, but enables more programs that don't compile today to compile in the future.