5 ms·
> I agree that Rust can look pretty weird to an untrained developer when lifetimes get involved. But, in Rust's defence, I haven't seen any other language write
by dietr1ch 2y ago
> I agree that Rust can look pretty weird to an untrained developer when lifetimes get involved. But, in Rust's defence, I haven't seen any other language write down lifetimes more concisely.
Can you do a lot better? I don't think so and it wouldn't help that much.
The truth is that most of the time we want to rely on some inferred lifetime annotations, but will obviously need an escape hatch from time to time.
Rust doesn't waste a lot of typing around the annotations, but if you were to improve Rust, you'd improve the implicit inference, not the syntax for being explicit.
- flohofwoe 2y ago> Can you do a lot better? I don't think so and it wouldn't help that much. I think Rust could do a lot better inferring lifetimes if the compiler would be allowed to peek into called function instead of stopping at the function signature - e.g. if it had a complete picture of the control flow of the entire code base (maybe be up to a point that manual lifetime annotations could be completely eliminated?). IMHO it's not unrealistic to treat the entire codebase as a single compilation unit, Zig does this for instance - it just doesn't do much so far with the additional information that could be gained.
- rcxdude 2y agoIt's a dangerous option: Rust already has long compile times, expanding the space it has to analyze would only increase that. Not to mention you'd be much more dependent on the implementation details of a given function, and it'd become very messy. The fact that lifetimes have a specifiable interface is probably one of the main things that makes Rust's approach work at all. Rust has similar rules about type inference (of which lifetimes are a subset) at the function level as well. I think this was a lesson learned the hard way by Haskell, which does allow whole-program type inference, and how programmers working in it quickly learned you really want to specify the types at the function level anyway
- flohofwoe 2y ago> Not to mention you'd be much more dependent on the implementation details of a given function Hmm, but wouldn't that already be the case since the manual lifetime annotation must match what the function actually does? E.g. I would expect compiler errors if the 'internal' lifetime details of a function no longer match its manual lifetime annotations (is it actually possible to create incorrect lifetime annoatations in Rust without the compiler noticing?) Higher compile times would be bad of course, but I wonder how much it would add in practice. It's a similar problem as LTO, just earlier in the compile process. E.g. maybe some time consuming tasks can be moved around instead of added on top.
- steveklabnik 2y ago> is it actually possible to create incorrect lifetime annoatations in Rust without the compiler noticing? In safe rust, no. Full inference is one of those things that seems like a no brainer, but there are a number of other more subtle tradeoffs that make it a not great idea. Speed was already mentioned, but it’s really downstream from tractibility, IMHO. That is, lifetime checking is effectively instantaneous today, and that’s because you only need to confirm that the body matches the signature, which is a very small and local problem. Once you allow inference, you end up needing to check not just the body, but also the bodies of every function called in your body, recursively, since you no longer know their signatures up front. We tend to think of compiler passes as “speed” in the sense of it’s nice to have fast compile times, but it also matters in the sense of what can practically be checked in a reasonable time. The cheaper a check, the more checks we can do. Furthermore, remember that Rust supports separate compilation, which is a major hindrance to full program analysis, which is what you need to truly infer lifetimes. Beyond complexity arguments, there’s also more practical ones: error messages would get way worse. More valid programs would be rejected if the inference can’t figure out an answer. Semver is harder to maintain, because a change in the body now changes the signature, and you may break your callers in ways you don’t realize at first.
- vlovich123 2y agoI would kill for Rust to spend some time figuring out what the ownership rule should be when I get the ownership wrong - compile cycles are cheap and inexpensive compared to me sitting & trying different approaches or running an LLM to try to help me figure it out (hint: they largely fail miserably and cause me to waste more time). I was fighting one function in my codebase and couldn’t figure out how to get the compiler to be happy despite me seemingly having a correct definition, so I just broke down and won the impasse by using unsafe which isn’t what I wanted to do. I know it sometimes recommends, but not in all cases and not in this particular case. Another thing I’ll point out is that TypeScript does full program inference and while type checking performance is a huge problem, it does a pretty good job. That obviously doesn’t necessarily map to Rust and the problem domain it’s solving (& maybe TS codebases naturally are smaller than Rust) but just putting that out there. Rust has made certain opinionated choices but that doesn’t mean that other choices weren’t equally valid and available. SemVer is easily solvable - don’t allow inference for pub APIs exported from the crate which also neatly largely solves the locality issue.