4 ms·
No CS degree here either. Variables exist in memory, right? But we only need them for some amount of time - then we want our memory back. There are many strate
by staticassertion 9y ago
No CS degree here either.
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.
- bluejekyll 9y agoExcellent description. 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#thinking-in-scopes 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.
- yorwba 9y agoNit > 'this thing lives this long and it can reference other things that live this long, or shorter'. 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.
- staticassertion 9y agoThanks, unfortunately I can't edit the post. :(
- deleted 9y ago[deleted]
- Twirrim 9y agoPossibly a stupid question, but is this a bit like reference counting, like you see in python, only done at compile time?
- mathw 9y agoNot really. Rust has an idea of "borrowing" references to things, which could be seen as increasing the reference count, but it's not a particularly good analogy because borrows actually have no effect on the original lifetime. 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.
- rtpg 9y agoIn python you can't really statically define a reference count 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.
- 9y ago
- rkangel 9y agoGreat explanation. The system also takes care of ownership moving around. In C you have functions that take a pointer to something, but you don't know who owns it (and is responsible for cleaning it up) after the function call, unless that is made explicit in the description. In Rust if you've moved ownership you can't use it in the caller afterwards, and if you haven't moved ownership you can't use it in the callee after the function has finished (this system is called borrowing, and is why the system that deals with lifetimes is called the 'borrow checker'). 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.
- nerdponx 9y agoInteresting. I have only worked in C a little bit, but I have seen the `const int foo(const int * x)` idiom a few times now. 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?
- sp332 9y agoThe checker in the compiler was being too conservative, because it was easier to write a conservative one. That means that sometimes it would complain about cases that were actually just fine. The language doesn't specify that lifetimes are tied to lexical scope, that's just what the borrow checker was using as a shortcut. The new one will be more specific.
- steveklabnik 9y agoThis is sort of true, but also not: it wasn't that it was easier to write a conservative one, it's that analysis is inherently conservative, and so we chose lexical scope because, well, that's what most languages do. 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.