> It's possible for a Rust program to be technically compilable but still semantically wrong.
This was my biggest problem when I used to write Rust. The article has a small example but when you start working on large codebases these problems pop up more frequently.
Everyone says the Rust compiler will save you from bugs like this but as the article shows you can compile bugs into your codebase and when you finally get an unrelated error you have to debug all the bugs in your code. Even the ones that were working previously.
> Rust does not know more about the semantics of your program than you do
Also this. Some people absolutely refuse to believe it though.
Needs a (2020) in the title. I don't think anything major is outdated, but in particular in section 10, one of the desired syntaxes is now supported as an unstable feature but there wasn't any mention of that:
#![feature(closure_lifetime_binder)]
fn main() {
let identity = for<'a> |x: &'a i32| -> &'a i32 { x };
}
I don't see how it's a misconception to say that a 'static lifetime lives for the life of the program. The author says "it can live arbitrarily long", which by definition must include... the life of the program. Where exactly is the error then?
My only complaint with this excellent list is that it treats "generics" and "lifetimes" as separate things. There's a reason the lifetime is inside the generic brackets. The code is generic over some lifetimes just as it can be generic over some types.
As a Rust beginner I read lifetimes backwards, thinking <'a> means I'm "declaring a lifetime" which I then use. What that actually declares is a placeholder for a lifetime the compiler will attempt to find wherever that struct or function is used, just as it would attempt to find a valid type for a type generic <T> at the points of usage.
Once I fixed that misconception everything made much more sense. Reminding myself that only the function signature matters, not the actual code, was the other thing I needed to really internalize.
The compiler messages hinder this sometimes, as when the compiler says "X doesn't live long enough" it actually means "using my limited and ever-evolving ability to infer possible lifetimes from your code, I can't find one that I can use here".
This is also (for me, anyway) a common "it's fine but it won't compile" case, where you don't have enough lifetime parameters. In other words, you're accidentally giving two things the same lifetime parameter when it's not actually necessary to require that the compiler come up with a single lifetime that works for both. The compiler error for that does not typically lead you to a solution directly.
I'm fairly out of touch with Rust. I think generics and lifetimes are also separate in the sense that only the generics get monomorphised, while lifetimes don't. I.e., you get distinct structs Foo<u32> and Foo<i32>, depending on the (type) argument with which Foo was instantiated (just like it is in C++), but only one Bar<'a> no matter what (lifetime) argument it was "instantiated" with.
Lifetimes and types are different, but the part where they are generic is the same. I think of it as "who controls/decides the value of this parameter". It's a crucial part of understanding lifetimes, not just a misconception.
Exactly, so it seems like a category error to say (as the article does) “generics and lifetimes are tightly intertwined in Rust”. I mean, would you say “generics and types are tightly intertwined in Rust”? If anything, types and lifetimes are intertwined — by generics!
Rust tutorials introduce lifetimes and stick them into the brackets, but I don’t remember one that explains genericity in a general way first, then applies it to both types and lifetimes. Lifetimes come across as a special thing that happens to be in the same brackets. (Or maybe that was just me!)
I believe the model in the compiler isn’t quite so pure in reality (IIRC, struct lifetimes can affect type unification), but most of the time this is the easiest way to understand it.
9 comments
[ 2.7 ms ] story [ 24.9 ms ] threadThis was my biggest problem when I used to write Rust. The article has a small example but when you start working on large codebases these problems pop up more frequently.
Everyone says the Rust compiler will save you from bugs like this but as the article shows you can compile bugs into your codebase and when you finally get an unrelated error you have to debug all the bugs in your code. Even the ones that were working previously.
> Rust does not know more about the semantics of your program than you do
Also this. Some people absolutely refuse to believe it though.
Contagious borrow issue is a common problem for beginners.
As a Rust beginner I read lifetimes backwards, thinking <'a> means I'm "declaring a lifetime" which I then use. What that actually declares is a placeholder for a lifetime the compiler will attempt to find wherever that struct or function is used, just as it would attempt to find a valid type for a type generic <T> at the points of usage.
Once I fixed that misconception everything made much more sense. Reminding myself that only the function signature matters, not the actual code, was the other thing I needed to really internalize.
The compiler messages hinder this sometimes, as when the compiler says "X doesn't live long enough" it actually means "using my limited and ever-evolving ability to infer possible lifetimes from your code, I can't find one that I can use here".
This is also (for me, anyway) a common "it's fine but it won't compile" case, where you don't have enough lifetime parameters. In other words, you're accidentally giving two things the same lifetime parameter when it's not actually necessary to require that the compiler come up with a single lifetime that works for both. The compiler error for that does not typically lead you to a solution directly.
Rust tutorials introduce lifetimes and stick them into the brackets, but I don’t remember one that explains genericity in a general way first, then applies it to both types and lifetimes. Lifetimes come across as a special thing that happens to be in the same brackets. (Or maybe that was just me!)
I believe the model in the compiler isn’t quite so pure in reality (IIRC, struct lifetimes can affect type unification), but most of the time this is the easiest way to understand it.