Can I avoid using explicit lifetime specifiers and instead use reference counting (Rc)?
Asked Answered
H

1

9

I am reading the Rust Book and everything was pretty simple to understand (thanks to the book's authors), until the section about lifetimes. I spent all day, reading a lot of articles on lifetimes and still I am very insecure about using them correctly.

What I do understand, though, is that the concept of explicit lifetime specifiers aims to solve the problem of dangling references. I also know that Rust has reference-counting smart pointers (Rc) which I believe is the same as shared_ptr in C++, which has the same purpose: to prevent dangling references.

Given that those lifetimes are so horrendous to me, and smart pointers are very familiar and comfortable for me (I used them in C++ a lot), can I avoid the lifetimes in favor of smart pointers? Or are lifetimes an inevitable thing that I'll have to understand and use in Rust code?

Hardshell answered 9/11, 2019 at 19:48 Comment(2)
You can to some extent use reference-counted pointers in your code to avoid lifetime annotations. However, you can't always avoid them when dealing with other people's crates (which is virtually always). And more importantly, your code will run against the gist of Rust, and you will run into different kinds of complexities that are even more difficult to deal with. So the short answer is probably "no".Weighbridge
You could use reference counted pointers - but those effectively just move the lifetime issues to runtime instead of compile time, trading performance for convenience. There are also some situations where they won't work - such as when exclusive/mutable access is required. I'd recommend learning to use lifetimes instead of relying on reference counted pointers - they're different and more difficult to understand, but they're foundational to a lot of higher level Rust concepts and you won't be able to avoid them for long.Messieurs
J
9

are lifetimes an inevitable thing that I'll have to understand and use in Rust code?

In order to read existing Rust code, you probably don't need to understand lifetimes. The borrow-checker understands them so if it compiles then they are correct and you can just review what the code does.

I am very insecure about using them correctly.

The most important thing to understand about lifetimes annotations is that they do nothing. Rather, they are a way to express to the compiler the relationship between references. For example, if an input and output to a function have the same lifetime, that means that the output contains a reference to the input (or part of it) and therefore is not allowed to live longer than the input. Using them "incorrectly" means that you are telling the compiler something about the lifetime of a reference which it can prove to be untrue - and it will give you an error, so there is nothing to be insecure about!

can I avoid the lifetimes in favor of smart pointers?

You could choose to avoid using references altogether and use Rc everywhere. You would be missing out on one of the big features of Rust: lifetimes and references form one of the most important zero-cost abstractions, which enable Rust to be fast and safe at the same time. There is code written in Rust that nobody would attempt to write in C/C++ because a human could never be absolutely certain that they haven't introduced a memory bug. Avoiding Rust references in favour of smart pointers will mostly result in slower code, because smart pointers have runtime overhead.

Many APIs use references. In order to use those APIs you will need to have at least some grasp of what is going on.

The best way to understand is just to write code and gain an intuition from what works and what doesn't. Rust's error messages are excellent and will help a lot with forming that intuition.

Julie answered 10/11, 2019 at 1:5 Comment(5)
Thank you so much! This gave me some confidence and direction, and I think I'll make further attempts to try to grasp lifetimes.Hardshell
After some sleep... Peter, can you please explain in two words why compiler requires me to annotate lifetimes, even if it can prove their correctness? Why wouldn't it "annotate" them without my help then, if it's so smart?Hardshell
In two words: no. In 70+ words... The type checker (including the borrow checker) does not look beyond function type boundaries. When you write a function type signature, you are declaring a two-sided contract which must be followed both by the function body and by any callers. The typechecker independently proves that both sides of the contract are honoured. Respecting type boundaries is a primary abstraction in Rust – and most other typed languages – and breaking it would make for fragile code.Julie
I am no expert in making programming languages, but it feels like they could do this type of checking by analyzing arguments on every calling of the function (i.e. looking beyond function boundaries). I am sure they have some technical obstacles to do that, though. Anyway, thank, you so much (for spending 70+ words on me :-) ), very accessible explanation!Hardshell
@NurbolAlpysbayev You may find it helpful to read Why are explicit lifetimes needed in Rust?Orelia

© 2022 - 2024 — McMap. All rights reserved.