3 ms·
> 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 th
by 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.
- steveklabnik 2y agoDid you check your unsafe with Miri? It's possible you were trying to do something that isn't actually possible, locally speaking. > I’ll point out is that TypeScript does full program inference Do you have a citation for this? I don't believe this is the case, though I could be wrong. I actually spent some time trying to find a definitive answer here and couldn't. That said, > Rust has made certain opinionated choices but that doesn’t mean that other choices weren’t equally valid and available. This is true for sure; for example, TypeScript is deliberately unsound, and that's a great choice for it, but does not make sense for Rust. > SemVer is easily solvable - don’t allow inference for pub APIs exported from the crate which also neatly largely solves the locality issue. It helps with locality but doesn't solve it, as it's still a non-local analysis. The same problems fundamentally remain, even if the scope is a bit reduced.
- vlovich123 2y agoHave you ever had success with Miri on non-trivial programs? Here's a reduced test case which does show it's safe under Miri but for the life of me I can't figure out how to get rid of the unsafe: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=a72ed781459fcb3126f8586b8c5adefe https://play.rust-lang.org/?version=stable&mode=debug&editio... > Do you have a citation for this? I don't believe this is the case, though I could be wrong. I actually spent some time trying to find a definitive answer here and couldn't. That said, No and thinking about it more I'm not sure about the specific requirements that constitutes full program inference so it's possible it's not. However, I do know that it infers the return type signatures of functions from the bodies. > This is true for sure; for example, TypeScript is deliberately unsound, and that's a great choice for it, but does not make sense for Rust Sure but I think we can agree that the deliberately unsound is for ergonomic and pragmatic compatibility with JS, not because of the choice of inference. I'm not arguing Rust should change it's inference strategy. Of all the things, I'd rate this quite low on my wishlist of "what would I change about how Rust works if I could wave a magic wand".
- cmrx64 2y agoNote that by creating the reference to a local and passing it up through the callback, you are using a fresh region that can’t possibly outlive any of one of the ones you are generic over. Fundamentally, that callback could stash the reference you pass it into state somewhere and now the pointer has escaped, invalidated as soon as that iteration of the loops ends. See that the definition of Group is tying those together. Instead, you can split them apart and maybe use HRTB to ensure the closure _must_ be able to treat the lifetime as fresh? But then you’ll probably have other issues… … which can largely be circumvented simply by pinning, in your reduced example, which probably doesn’t retain enough detail. https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=b05e99cce303001b0e10fda815369457 https://play.rust-lang.org/?version=stable&mode=debug&editio... My suggestion to avoid the pain? Use ArcStr.