I agree with @amosbatto and @parasyte, The hard thing isn’t borrow rules things, The biggest pain point with the language is living with lifetimes, It should in compiler than in language syntax, we shoule learn rules but not to write it.
Lifetimes are part of the interface contract of a type or function, because they determine what data can be passed in to a function or inserted into objects of a certain type. In this sense they should be exposed by the language’s syntax. Otherwise, a modification of a type’s or function’s implementation could silently break code that uses it, a subtle compatibility bug which the design of Rust has taken great steps to avoid.
If you do not want to expose the lifetime information in the interface, you need to use abstractions which hide lifetimes from the user and manage them at run time, such as reference-counted pointers. But these abstractions come at a performance cost, which is why they should not be used at the most fundamental layer of a system programming language.
It is true, however, that we should keep the associated syntax minimal in the common case. For functions, this work has already been done with lifetime elision: these days, you only need to spell out a lifetime in a function signature in cases where there is an interface ambiguity. But for types, there is more work to be done, some of which is currently ongoing as part of the ergonomics initiative (I think @nikomatsakis could say more about this).
Hey guys, I was just staring out learning rust and its my first low level language. I thought I will share my opinion as a noob here! So, after learning the basics of Rust, I found it interesting and asked my wife to join me in learning and we are both collaborating. She has already worked on c and c++ but never touched Rust before. So, after I explained the basics and we tried to compile the basic code she was like
Holy shit! This compiler is like my dad, won't let me do anything wrong!!
I guess, that summarizes both of our opinion towards Rust. However different or complex it is from other languages, Rust makes sure you are going to write safe and efficient code not after 10 years of experience but from day one! and its a very valuable feature for a language like Rust to become widely adopted even though its difficult.
TWiR quote of the week
TWiR quote of the week
My experience has also been that that lifetimes are the main challenge with rust. When I thought about why I found that it’s because I did not have a complete or clear model of what a lifetime is. I’d start with one model and then observe something that invalidated it. This kept on happening until I grokked it more and more. I don’t think I still fully understand it, but it’s a lot better and as a result I find a lot easier to get my code to compile now.
I have posted my share of “Rust is overly difficult and underly documented” comments (yes, I’m aware I just made up a word).
But I’ve persisted for two reasons:
- the rigor of the language in insuring that you don’t write bugs that can be extremely difficult to find, and
- the helpfulness of the Rust community (having once used OpenBSD – no longer – I’ve seen the opposite in action and it’s really ugly).
And having persisted, what has happened is I’ve found my own subset of Rust that I use that tends to avoid the difficulties in which one can find oneself regarding borrowing and lifetimes. So while there are parts of this language that can drive you crazy, there are also parts of the language that let you avoid unnecessarily getting into that kind of trouble.
In my own situation, the difficulty that pushed me to the brink of throwing in the towel was an application that requires mutable global state – switches, hash tables, sqlite statements that have been prepared (or not), as well as the need to make these things available to gtk callbacks, which must be closures with static lifetimes. Etc. I thrashed around, trying passing a global structure around, trying mutable statics, lazy statics, thread local storage, etc. It took awhile (and a lot of help from Vitaly and hadrieng), but I finally developed the mental model of what Rust is doing that allowed me to find a working solution (avoiding statics, which are a nightmare, using a global structure and judicious use of Rcs and RefCells, and knowing when to pass a reference to the structure vs giving ownership and when the latter, when to use shared ownership via Rcs proved to be the key).
While I’ve argued that Rust’s avoidance of garbage collection exacts too high a price to justify its use in general application development, it may be that if you find the fortitude to traverse your own learning curve, once you reach a level of mastery where you are not screaming at the compiler (which could not care less :-), your productivity in Rust could be comparable to, say, Haskell, which provides similar rigor (in both Rust and Haskell, when your code compiles – FINALLY! – you are usually within logic errors of correctness; no memory-management bugs that bite you at runtime).
And something else that makes a difference is the quality of the tools. Cargo is really a huge asset to Rust. its Haskell equivalent, Cabal, is simply awful. Managing updates of the Rust compiler is a simple matter with Rustup. Unfortunately, the GHC people have gotten themselves into a bit of a mess with dynamic vs. static linking, or perhaps its just Arch Linux that has created the problem, but at least Arch/Haskell is a complete mess right now. This is one of the reasons why I’ve persisted with Rust. I know Haskell well and admire the language, but the compiler/tool situation is not good right now.
Summarizing, my message is that Rust can look formidably difficult. But with help from the good folks here and some persistence, it’s possible to develop the mental model you need and most importantly, find the subset of the language that works for you.
The biggest pain point with the language is living with lifetimes, It should in compiler than in language syntax
Actually, I don’t think the compiler should necessarily be responsible for fixing the challenge with describing and managing lifetimes. I believe that developers should first learn how to use the language without them. This is more of an educational burden, or even one of discipline; it goes both ways. The Rust team can help with education (and maybe some ergonomic improvements along the way), but developers still need to be aware of and understand that explicit lifetimes are a fact of life.
When I was first learning C decades ago, the biggest learning curve was all around pointers. In a lot of ways it mirrors my experience with Rust and explicit lifetimes. But the difference between the two is that C encourages the use of pointers (which are notoriously unsafe) and Rust makes using explicit lifetimes such a pain that it’s almost a dissuasion.
I’d be curious to see (via new threads) the lifetime related questions that people have.
Any examples? Or more elaboration on specific pain points? I suspect most of the trouble is when mutable references are involved, which create situations where the compiler can’t squeeze/coerce lifetimes of invariant types and thus more explicit control of lifetime parameters (and bounds) is needed.
Curious which bits you think you’re still fuzzy on?
Can we maybe have a book or booklet exclusively covering lifetimes? I don’t think the first level of instruction material on lifetimes which is found in the rust book and others which talks about the syntax and the aliasing rules and elision is enough. It leads to an incomplete model which only frustrates when you discover its incompleteness. Rust nominoc does go further. For example it shows with detailed examples how lifetimes start with a let binding and how they interact with other lifetimes in the same scope. This is fundamental stuff and absolutely essential to understanding. But there are still aspects not covered there. For instance I realized that lifetimes can be shrunk as needed by the compiler only from this forum (thanks @vitalyd) which invalidated my previous model. And I’m sure there are other aspects I don’t know about. I think we just need one place where one can learn everything about lifetimes and be done with it.
The nomicon covers this (to some degree) under the variance/subtyping section but I agree that there’s no single place to get “all lifetime related things” in one place.
Any examples? Or more elaboration on specific pain points?
Just the general “viral” nature of lifetime annotations when attempting to store a reference in a struct. Any method that accesses those fields also need the same annotations. (I read recently that this may have changed? Lifetime elision is really nice when it can be used!)
For a specific example, the
sdl2 crate has a
Texture struct which requires an explicit lifetime. These structs are owned by a
TextureCreator, which was supposed to alleviate this issue, but in practice does little. Storing a
Texture in a struct requires the same lifetime annotation, as does every method that uses the texture. This specific example has come up in their issue tracker, which is an additional resource for material covering user experience problems with explicit lifetimes. edit: Here’s another one.
If I’m not mistaken, this “explicit lifetime propagation” also begins breaking semver compatibility. Which could be a show stopper, depending on the context.
Both sides are possible, depending how you interpret the question.
Is Rust too difficult to become as broadly-used as something like Python? Probably. The majority of the code in the world doesn’t need that last 10% of control and rigor that’s the pay-off for the extra complexity.
Is Rust too difficult to supplant C++ as the default choice for systems programming? I’ve actually argued that Rust is easier than C++, so no. Having the compiler check more things is incredibly freeing in what you can just do without needing to stress over it.
Vast majority of cases will not require repeating the lifetime parameter of a field in a method of a struct holding that reference.
The sdl2 stuff I’ve seen on this forum as well - they all boil down to self referential structs, which is a known pain point. I suspect some domains (like graphics) tend to rub up against this much more so than others.
Lifetimes are contagious, no different than generic type parameters though. The notable difference is generic types can be erased in some cases (ie trait objects) whereas you can’t erase lifetimes. So that part is true. I suppose the closest analog to erasing lifetimes is to use owned objects instead, either passing things by value or putting them behind a pointer.
Exactly. This is what I have been alluding to above. Using this “subset” of Rust is a breeze. I also commented on explicit lifetimes eventually leading to self-referential structs in the other thread.
But how often do you actually hit self referential cases (sincere question)? Just like trait objects should be relatively rare, I suspect most lifetime situations are relatively straightforward (think immutable iterators over a borrowed container). There are definitely libs that make it hard to use lifetimed types in certain cases (think tokio and higher layers on top), and so you learn the
Rc<RefCell<...>> approach. So I think, perhaps, part of the learning challenge is knowing which approach to take when, rather than being afraid of one necessarily. This boils down to experience with rust and these libs - I don’t think there’s an easy way out.
Serious answer: Never, since I stopped storing references. I also put my Tokio-based project on hold due to the same class of issues (with borrows across yield points – I know progress has been made on this in nightly). As I mentioned before, I think there are ways to lessen the learning curve and provide better educational opportunities. I also don’t think
Rc<RefCell<...>> is at all ergonomic (and I don’t believe you implied that it is). For that you trade propagating explicit lifetimes for propagating
.barrow_mut() everywhere. But at least it doesn’t contaminate the semantic versioning of public APIs.
There is an entire second section as well, that covers more advanced topics.
I think a mini-book on lifetimes would be great, but it’s hard to know what it should actually cover. And it should probably wait until after NLL lands.
Thanks @steveklabnik. I saw the second edition book, but I feel the lifetimes coverage can still use some expansion.
About the mini-book, I think it should cover anything and everything needed to be proficient in the area. (because you do kind need to know everything to be that way as I’ve found). Importantly, I feel the book should lean a lot on illustrations and examples. We can scour through this forum and see all the different roadblocks people have hit. They could form the different “patterns” which the book should treat individually. For instance: this is scenario A where you’d typically stumble and this is how you get out of scenario A and so on. It’ll be great resource for new people and may help flatten the learning curve a lot (since most of it consists of the topic of lifetimes anyway).
See, this is too broad: I need specific things to cover. Everyone agrees that we should do what you’ve said, but nobody is sure what exactly that is, you know?