As a C++ programmer, I understand ownership and borrowing, but I find the lifetime syntax impenetrable. It clutters the code with annotations that seem like they could be deduced by the compiler.
For example, a variable's lifetime is relative to another variable or function. Why must we create a placeholder name like 'a instead of referring to the other variable by name? Take this example from section 10.3:
Why couldn't we write it as something like the following?
fn longest(x: &str, y: &str) -> &'x str {
x
}
Or instead of requiring lifetimes everywhere, the compiler could use conservative defaults but allow optional lifetime annotations to reduce the lifetime if needed or to release memory sooner. For example, in this struct from section 10.3, why is 'a necessary when config is a non-mut reference and thus must outlive a struct App instance? There is a Rust issue filed for this: https://github.com/rust-lang/rfcs/issues/1532
The Rust community prefers explicitness in many cases. We already have lifetime elision for simpler things. Both of these cases in your comment have been extensively discussed and decided against.
The reason why the former is not done is because it makes the signature dependent on the body. This means that changing some code could potentially change the signature of the function, which is the kind of silent magic that folks would prefer to be explicit. Also, as the function gets larger, this is harder to deduce by looking at the code. The current elision rules all operate on the signature, so you only need to look at the signature to figure out the lifetimes, elision or not.
The latter is way more controversial. The reason there is similar; folks want it to be obvious from the type name that it is a borrowing type, and changes to the internals shouldn't magically change the signature without an explicit acknowledgement.
Even type arg lifetime elision was controversial. Currently, in Rust, you can write a function like `fn foo(x: &u8) -> Foo`, where `Foo` is actually `Foo<'a>`, and the compiler inserts the lifetimes in the right places. When this was proposed there was a good chance that it would not happen (though as you can see it did happen). So the status quo on lifetime elision is probably not going to change in my opinion, given how hard it was to make the last one happen (of course, you have the "overton window" of acceptable implicitness gradually shifting due to that change, so it might after all)
To me personally, this explicitness is a minor annoyance when dealing with simpler stuff, but is invaluable in codebases making heavier use of lifetimes, like rustc (which avoids reference counting, even though compilers generally need a lot of tricky sharing). And ultimately, I appreciate it even in codebases with fewer lifetimes; I don't like having to peek at a function's code to understand how it's supposed to be used.
No lifetime parameters can be shorter than the lifetime of the struct itself, and that's part of the so-called WF (well-formed(ness)) rules in Rust (which the compiler tries to make as implicit as possible).
However, that has nothing to do with why you have to have a parameter. The parameter is there to make all instances of App track the actual lifetime, e.g. you can tell between App<'foo> and App<'static> (and in the former case, it can keep the borrow that made the &'foo Config alive for as long as the App<'foo> sticks around, and if this is longer than the underlying data lives for, you get an "use after free" error).
Combined with function signatures, Rust can track complex interactions without ever looking at callee bodies, and if you're never reusing a lifetime parameter, you get just as much flexibility as if you were passing the references around directly.
This is something that the C++ attempts at tracking scopes and enforcing a set of rules about them, don't seem to have figure out yet. You need a certain amount of annotations to keep the correct mapping, otherwise you just lose information and have to assume every struct that contains a reference may borrow everything that was borrowed at some point and the borrow may have "escaped" into a struct.
You're either too conservative and thus can't apply the rules to prevent iterator invalidation and data races like Rust can with borrows (Cyclone didn't have borrow-checking either IIRC), or you're ignoring an entire subset of UAF bugs waiting to happen.
Even for the purpose of preventing UAF, such an imprecise aliasing-analysis-like conservative system may be too restricting for many real-world usecases, whereas Rust's, ironically, wouldn't be.
I thought the same thing (especially about structs), but after some time with Rust I actually like the explicitness about lifetime rules.
As soon as you are not fighting with lifetimes anymore, it's just a bit more to type. IMHO it actually makes code easier to read, since I don't have to remember a lot of weird rules.
But I have to admit that Rust already has some rules for lifetime elision (e.g. `fn getx(&self) -> &T { &self.x }`. I can only speak for myself but IMHO these rules have a pretty good cost/benefit ratio. For me introducing more rules adds just more cost for diminishing value.
> For example, a variable's lifetime is relative to another variable or function. Why must we create a placeholder name like 'a instead of referring to the other variable by name?
Because they're different namespaces? &'x refers to the lifetime x, which has no relation to the local variable x, the global x, the type x or the module x.
Reading that, and the little blurb about move and copy semantics below the infographic, it occurred to me that borrowing is actually rather easily described as real like sharing (which is unfortunately already a loaded term in much of CS due to multiprocessing).
When you share something that is not copyable (such as a pen), you give it to the person borrowing it until they return it when done.
When you share something that is copyable (and idea, some software, a joke), you give someone their own copy to do with as they please. There is no return required.
Borrowing, which is from the receiver's perspective, implies returning, while sharing, from the sender's perspective, may or not mat imply a later return of something.
I wonder how much the conceptual baggage of the terminology affects how people learn them.
Okay, that's really cool and does probably the best job I've seen of an overview of &mut and &.
I feel like you could almost build a rust cheat-sheet poster out of this and few other useful syntax visualizations(traits, generic + lifetime syntax, etc).
And to be clear, 10.3 is just the intro; we have a full chapter later with the complicated stuff.
We decided to do it this way so that you can write real Rust code without going "what is that syntax"; you don't need to have a full understanding of the more exotic aspects of lifetimes to write useful code in Rust.