top | item 46270882

Common Rust Lifetime Misconceptions

137 points| CafeRacer | 2 months ago |github.com

81 comments

order

wrs|2 months ago

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.

estebank|2 months ago

If you have a good repro case, I'd appreciate a bug report. Bad diagnostics are considered bugs.

penguin_booze|2 months ago

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.

vacuity|2 months ago

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.

yuriks|2 months ago

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 };
    }

bigstrat2003|2 months ago

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?

oersted|2 months ago

Because something with 'static lifetime does not in fact live for the entire program.

It just means that it could live until the end of the program and that case should be considered when dealing with it, there's no guarantee that it will drop earlier. But it may drop at any time, as long as there are no remaining references to it, it does not need to be in memory forever.

It's a subtle distinction and it is easy to misinterpret. For instance Tokio tasks are 'static and it felt wrong initially because I thought it would never drop them and leak memory. But it just means that it doesn't know when they will be dropped and it cannot make any promises about it., that's all.

jayd16|2 months ago

> Well yes, but a type with a 'static lifetime is different from a type bounded by a 'static lifetime. The latter can be dynamically allocated at run-time, can be safely and freely mutated, can be dropped, and can live for arbitrary durations.

yuriks|2 months ago

A 'static lifetime does not live for the rest of the program. It rather is guaranteed to live for as long as anyone is able to observe it. Data allocated in an Rc for example, lives as long as there are references to it. The ref count will keep it alive, but it will in fact still be deallocated once all references are gone (and it cannot be observed anymore).

enricozb|2 months ago

I think if the compiler determines that it can drop a 'static, because nothing uses it after a certain point, it may drop it.

nickitolas|2 months ago

"life of the program" might imply it needs to begin life at program start. But it can be allocated at runtime, like an example in the list shows. So its rather "lives until the end of the program", but it doesnt need to start life at the start of the program

wrs|2 months ago

"Arbitrarily long" means "however long any code needs it to live", not "whatever lifetime a human reading the code can conceive of".

nromiun|2 months ago

> 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.

MrJohz|2 months ago

I think the key idea is that Rust gives you a lot of tools to encode semantics into your program. So you've got a much greater ability for the compiler to understand your semantics than in a language like JavaScript (say) where the compiler has very little way of knowing any information about lifetimes.

However, you've still got to do that job of encoding the semantics. Moreover, the default semantics may not necessarily be the semantics you are interested in. So you need to understand the default semantics enough to know when you need something different. This is the big disadvantage of lifetime elision: in most cases it works well, but it creates defaults that may not be what you're after.

The other side is that sometimes the semantics you want to encode can't be expressed in the type system, either because the type system explicitly disallows them, or because it doesn't comprehend them. At this point you start running into issues like disjoint borrows, where you know two attributes in a struct can be borrowed independently, but it's very difficult to express this to the compiler.

That said, I think Rust gives you more power to express semantics in the type system than a lot of other languages (particularly a lot of more mainstream languages) which I think is what gives rise to this idea that "if it compiles, it works". The more you express, the more likely that statement is to be true, although the more you need to check that what you've expressed does match the semantics you're aiming for.

LtdJorge|2 months ago

Yes, that's a very common misconception.

Of course, if your program compiles, that doesn't mean the logic is correct. However, if your program compiles _and_ the logic is correct, there's a high likelihood that your program won't crash (provided you handle errors and such, you cannot trust data coming from outside, allocations to always work, etc). In Rust's case, this means that the compiler is much more restrictive, exhaustive and pedantic than others like C's and C++'s.

In those languages, correct logic and getting the program to compile doesn't guarantee you are free from data races or segmentation faults.

Also, Rust's type system being so strong, it allows you to encode so many invariants that it makes implementing the correct logic easier (although not simpler).

littlestymaar|2 months ago

I don't think anyone believes the “if it compile it works” phrase literally.

It's just that once it compiles Rust code will work more often than most languages, but that doesn't mean Rust code will automatically be bug free and I don't think anyone believes that.

treyd|2 months ago

> Some people absolutely refuse to believe it though.

Who says this? I've never seen someone argue it makes it impossible to write incorrect code. If that were the case then there's no reason for it to have an integrated unit testing system. That would be an absurd statement to make, even if you can encode the entire program spec into the type system, there's always the possibly the description of a solution is not aligned with the problem being solved.

alkonaut|2 months ago

"If it compiles it works" isn't true. But "If it compiles it won't eat your homework" sort of is.

hsywuwuf|2 months ago

[deleted]

simonask|2 months ago

FTA:

> Others think someone from the Rust (programming language, not video game) development community was responsible due to how critical René has been of that project, but those claims are entirely unsubstantiated.

What is this culture war you're fighting?

Tuna-Fish|2 months ago

Rebe isn't blaming this on rust proponents, but on a troll he banned 30 minutes before he got swatted. Where are you getting the rust connection from?