[HN Gopher] Lifetime Annotations for C++
       ___________________________________________________________________
        
       Lifetime Annotations for C++
        
       Author : akling
       Score  : 141 points
       Date   : 2022-04-02 13:35 UTC (9 hours ago)
        
 (HTM) web link (discourse.llvm.org)
 (TXT) w3m dump (discourse.llvm.org)
        
       | clock99 wrote:
        
       | malkia wrote:
       | April fools, right? Like std::weekend_ptr - holds the ptr only
       | during the weekend....
        
         | glouwbug wrote:
         | Sounds as useful as most pointers to be honest
        
           | glouwbug wrote:
           | Downvote all you like. Memory managed languages pay the bills
        
         | shamilatesoglu wrote:
         | :D. Is this really a thing, or like a joke between friends? I
         | googled it but nothing came up.
        
       | tialaramex wrote:
       | Maybe a C++ person can help me out, I am staring at this C++
       | translation of Rust's elision rules:
       | 
       | > If there are multiple input lifetimes but one of them applies
       | to the implicit this parameter, that lifetime is assigned to all
       | elided output lifetimes.
       | 
       | In Rust we have _self_ rather than _this_ in methods, but
       | importantly we sometimes don 't take a reference here, and that's
       | _still_ a method, and you still have the _self_ parameter, but
       | the lifetime elision rules don 't apply. They don't apply because
       | if you've actually got self, not some reference to self, the
       | lifetime of self is going to end when the function finishes, so
       | returning things with that lifetime is nonsense.
       | 
       | This can make sense in Rust for transformative methods. If
       | there's a method on a God that turns them into a Mortal, the God
       | doesn't exist any more when that method exits, the Mortal is the
       | return type, and if you just drop it then I guess sucks to be
       | them. (In Rust, and I think in C++ you can label something as
       | must-use and cause a warning or error if the programmer forgets
       | to use it).
       | 
       | It seems though, as if this C++ rule would hit such methods too.
       | Can you distinguish in C++ between "a reference to me" and "me"
       | when it comes to methods? If you had a method on a God class in
       | C++ that transforms the God into a Mortal and returns the Mortal,
       | what does that look like? Does this elision rule make sense to
       | you for such a scenario?
        
         | Rusky wrote:
         | C++ does not currently have such methods- all `this` parameters
         | are always by (some kind of) reference. (This doesn't stop you
         | from moving out of `*this`, but C++ moves require the source
         | object to remain valid, and I'm not sure I've ever seen that in
         | the wild anyway!)
         | 
         | This will change in C++23 with "explicit this", but then the
         | `this` parameter will no longer be implicit and that translated
         | elision rule will no longer apply either.
         | 
         | So in today's C++ your example might look something like this:
         | struct God {             Mortal transform() && { // Take an
         | rvalue reference for `*this`, I guess?                 return
         | Mortal{ /* Move some fields out of `*this` probably? */ };
         | }         };
         | 
         | The elision rule does apply here even though we're moving out
         | of `*this`, but again we are still taking a reference and the
         | God object remains valid afterward anyway.
         | 
         | With "explicit this" you could instead write something like
         | this:                   struct God {             Mortal
         | transform(this God self) { ... }         };
         | 
         | Now there's no reference and the rule ceases to apply. (Though
         | in most cases you are _still_ going to be leaving a valid, but
         | now at least unrelated, God object behind- the only way to
         | avoid that is to construct it directly in argument position so
         | it stays a prvalue.)
        
           | enedil wrote:
           | In our codebase though we have some places where we do
           | `delete this;` and later use placement new, to restore `this`
           | to some valid state.
        
       | htfy96 wrote:
       | This reminds me of the -Wlifetime proposal, which provides
       | similar checks but requires annotation of ownership at struct
       | level (hence the check only applies to new structs):
       | 
       | An example: https://github.com/llvm/llvm-
       | project/blob/main/clang/test/Se...
       | 
       | More details can be found at
       | https://herbsutter.com/2018/09/20/lifetime-profile-v1-0-post...
       | 
       | Unfortunately it was never upstreamed according to
       | https://github.com/mgehre/llvm-project/issues/98
        
         | Rusky wrote:
         | There's a lot of overlap between the authors and implementors,
         | and this proposal has extensive comparisons with the -Wlifetime
         | one!
        
       | c0balt wrote:
       | Sounds awesome. After the initial hurdle of getting used to
       | lifetimes in rust it was really an enjoyable time and I would
       | love to see the same feature in c++.
        
       | kjksf wrote:
       | It's a bad sign that I can't tell if someone spent a lot of time
       | on an elaborate April's Fool joke or if it's a serious C++
       | proposal.
        
         | [deleted]
        
         | [deleted]
        
         | SilasX wrote:
         | It's a bad sign in general that adding long-needed safeguards
         | to C++ can be considered a joke.
        
           | qualudeheart wrote:
           | C++ should double down on high performance. Better support
           | for inline assembly. More parallelism and concurrency.
           | Specialization.
        
       | verdagon wrote:
       | > To summarize, enforcing [Rust's] borrowing rule in C++ is
       | unfortunately not so simple because there is a lot of existing
       | code that creates multiple non-const pointers or references to
       | the same object, intentionally violating the borrowing rule. At
       | this point we don't have a plan of how we could incrementally
       | roll out the borrowing rule to existing C++ code, but it is a
       | very interesting direction for future work.
       | 
       | This is actually possible for C++, if we add a concept of pure
       | functions: functions that don't modify anything indirectly
       | reachable from arguments (or globals). Basically:
       | 
       | * Annotate those parameters as "immutable" or as coming from the
       | same immutable "region". Anything reached through them would also
       | have that immutable annotation.
       | 
       | * Anything created during the call would not have that
       | annotation.
       | 
       | The type system, basically a borrow checker, could keep them
       | separate and make sure we don't modify anything that came from
       | outside the function.
       | 
       | We're currently adding this to Vale [0], It's a way to blend
       | shared mutability with borrow checking, and it gives us the
       | optimization power and memory safety of borrow checking without
       | all the constraints and learning curve problems.
       | 
       | [0]: https://verdagon.dev/blog/seamless-fearless-structured-
       | concu...
        
         | Rusky wrote:
         | That's not the issue here- there are plenty of schemes that
         | could be used to enforce the rule, including just copying
         | Rust's if you wanted.
         | 
         | The issue is that a bunch of existing code actively holds _and
         | uses_ multiple mutable references to the same objects. It would
         | simply not be able to adopt the chosen scheme, regardless of
         | how it 's spelled.
        
           | verdagon wrote:
           | That's what this "region borrow checking" solves: if a pure
           | function (or block) regards all previously existing memory as
           | one immutable region, it doesn't matter if there was any
           | mutable aliasing happening before, because everything inside
           | that region is shared and immutable now.
           | 
           | Don't get me wrong, it's not the only missing piece for C++;
           | C++ lets pointers escape to other threads which might modify
           | the objects while our thread considers them immutable. Vale
           | solves this by isolating threads' memory from each other
           | (except in the case of mutexes or Seamless Concurrency).
           | Luckily, there are plenty of schemes that can solve that
           | particular problem for C++.
           | 
           | If I had infinite time I would love to figure out how to
           | implement this into C++, after the proof-of-concept in Vale.
           | It's a fascinating topic, and an exciting time in the memory
           | safety field, full of possibilities =)
        
         | hardlianotion wrote:
         | Sorry for silly question, but I have been away from C++ for a
         | long time now. How do these immutable function parameters
         | properties differ from const parameters?
        
           | WalterBright wrote:
           | In D, we discovered both const and immutable annotations were
           | required. Immutable means the object never changes. Const
           | means the object cannot be altered via the const reference,
           | but can be altered by mutable reference to the same object.
           | This makes optimization based on immutability possible, it
           | also means immutable objects can be shared among multiple
           | threads without synchronization.
           | 
           | Both const and immutable attributes are transitive, meaning
           | they are "turtles all the way down." People who are used to C
           | and C++'s non-transitive const find it a bit difficult to get
           | used to; people who have used functional languages find it
           | liberating, and it makes for much easier to understand code.
        
           | klyrs wrote:
           | I use C++ daily and have the same question.
        
           | injidup wrote:
           | A const object can still have a const pointer to a non const
           | object. It's a source of much confusion and I've seen such
           | errors often in our own code base.
           | 
           | Immutability is not so simple in C++ as it might first
           | appear.
        
             | hardlianotion wrote:
             | We can declare a const pointer to a const object -
             | irritating ceremony but still possible.
        
             | kllrnohj wrote:
             | A const object can also still have mutable data. const_cast
             | can just remove the 'const' attribute entirely, but even
             | ignoring that "abuse" there's also the 'mutable' keyword
             | which allows fields to be modifiable on const objects.
             | 
             | https://en.cppreference.com/w/cpp/language/cv
        
               | zozbot234 wrote:
               | The rationale for "mutable" is similar to the one for
               | interior mutability in Rust. It's actually really hard to
               | define what it means for something to be "constant" in a
               | systems language, and that's why Rust did not consider
               | deeper sorts of immutability or functional purity.
        
               | fwsgonzo wrote:
               | mutable is used primarily for memoization:
               | 
               | https://en.wikipedia.org/wiki/Memoization
        
           | verdagon wrote:
           | It's because const pointers aren't "deeply" immutable. For
           | example, if we have a const Car*, we can reach into it to
           | grab a (non-const) Engine* through which we can modify
           | things.
           | 
           | If there was a "imm" keyword in C++ which acted "deeply",
           | that would get us pretty far towards our goal here. However,
           | we'd then find ourselves in cases where we need to (for
           | example) cast from an imm Engine* back to a (non-const)
           | Engine*, often for values returned from functions. That's
           | what this new "region borrow checker" concept would solve.
        
             | WalterBright wrote:
             | In D we call "deeply" immutable "transitive immutable".
             | 
             | Interestingly, a transitive immutable data structure can be
             | implicitly converted to transitive const, but not vice
             | versa.
        
       | summerlight wrote:
       | Looks like at least some of the authors are working for Google
       | (if not everyone), with compiler backgrounds. I wonder if this is
       | a strategical investment from Google to make cxx like approach
       | more robust? Chrome team showed some interests on using Rust but
       | not sure if there's any significant code written in Rust from
       | then. Benefit from using Rust beside well isolated library might
       | be quite limited without this kind of lifetime annotation while
       | interop itself is additional cognitive overheads for programmers.
        
         | verdagon wrote:
         | I think it makes the most sense for Google, considering the
         | _ridiculous_ amount of C++ code. Even Spanner alone is larger
         | than most company 's codebases, and rewriting it in Rust would
         | take decades, especially because of the paradigm mismatch (C++
         | embraces shared mutability, Rust rejects it).
         | 
         | It also makes sense because in Spanner, doing any minor change
         | required many months of review, because it was such critical
         | infrastructure. Refactoring was a non-starter in a lot of
         | cases.
         | 
         | So, a more gradual approach, just adding annotations that don't
         | themselves affect the behavior of the program (just assist in
         | static analysis) makes much more sense.
        
           | zozbot234 wrote:
           | Rust does not reject shared mutability. Rather, it is
           | explicitly _reified_ via the Cell <>, RefCell<>, etc.
           | patterns. Even _unsafe_ shared mutability ala idiomatic C++
           | is just an UnsafeCell <> away.
        
             | verdagon wrote:
             | In theory yes, but when you look at the average Rust
             | program, those mechanisms are avoided in favor of more
             | idiomatic approaches.
        
               | hgomersall wrote:
               | Interior mutability is used all over the place. It's
               | absolutely necessary. The thing is you are forced to be
               | clear about the runtime safety mechanism you're using. In
               | general, it gets abstracted away behind a safe API.
        
               | verdagon wrote:
               | Indeed it's present under the hood, we are operating on a
               | CPU after all, which treats all of RAM as one giant
               | shared-mutable blob of data. It will always be there,
               | under some abstraction.
               | 
               | The point I'm trying to communicate is that in practice,
               | for various reasons, Rust programs do not use shared
               | mutable access to objects to the same extent that C++
               | programs do. For example, C++ programs use observers, and
               | dependency injection (the pattern, not the framework) to
               | have member pointers to mutable subsystems, and we just
               | don't often see that in Rust programs. This is the
               | paradigm mismatch I'm highlighting: to rewrite a C++
               | program in Rust often requires a large paradigm shift.
               | The pain is particularly felt when making them
               | interoperate in a gradual refactoring.
               | 
               | This is IMO one the bigger reasons that big rewrites to
               | new languages fail, and why new languages benefit from
               | being multi-paradigm, so that there's no paradigm
               | mismatch to impede migrations.
        
             | [deleted]
        
       | lamp987 wrote:
       | 10 years from now, there will be nobody on earth able to say "i
       | know c++" because each codebase will use a completely different
       | set of bazillion optional features making each codebase look like
       | an entirely different language from each other.
       | 
       | but maybe thats how we'll finally get rid of c++.
        
         | mattgreenrocks wrote:
         | > each codebase will use a completely different set of
         | bazillion optional features making each codebase look like an
         | entirely different language
         | 
         | The issue is each codebase has its own DSL?
         | 
         | That is pretty close to the case with all non-C++ langs:
         | industry settles on a handful of frameworks which are
         | syntactically similar but each has their own gotchas.
        
           | jcelerier wrote:
           | > The issue is each codebase has its own DSL
           | 
           | It's not "an issue", it's in my opinion the very best
           | development methodology there is: develop eDSLs for every
           | sub-problems in the system
        
         | jurschreuder wrote:
         | This might sound kinda weird, but I really like reading about
         | coding languages and with c++ there is so much to discover that
         | I really like that. It's like buying an expensive car with a
         | lot of options and even after years you discover new buttons.
         | If you like reading about low level code and implementations of
         | recent new techniques in computer science, then you'll love
         | c++, there is always so much going on. I've learned almost all
         | coding languages and they've become simple and boring, articles
         | about them feel like reading a children's book. It's definitely
         | not easy to learn all features, but it also doesn't become
         | boring so fast.
        
           | krelian wrote:
           | I know that feeling. There is joy is reading a good technical
           | manual. Enjoyment in seeing a complex system built up. How
           | the parts support each other and intermix to produce
           | something complex and intricate. Once you have read it all
           | and have in your head a complete mental image of how it
           | works, when you can open the manual to any page and not feel
           | lost, there is additional enjoyment and sense of
           | completeness.
        
         | ben-schaaf wrote:
         | I'd argue we've already been there for a while with everyone
         | using a different subset of C++. I don't think it's made the
         | language less popular.
        
           | pjmlp wrote:
           | If anything other languages are catching up in complexity.
        
         | [deleted]
        
         | pjmlp wrote:
         | I have to agree, but then we need replacements for everything
         | that is written in C++, like LLVM.
        
         | klyrs wrote:
         | > but maybe thats how we'll finally get rid of c++.
         | 
         | I feel like you didn't read your own comment here. What you've
         | described is a world that is littered with so many c++
         | variants, it will be impossible to eradicate them.
        
           | replygirl wrote:
           | The more variants there are, the less active work is required
           | to EOL every variant. Let's be flavor accelerationists.
        
         | Mikeb85 wrote:
         | > 10 years from now, there will be nobody on earth able to say
         | "i know c++" because each codebase will use a completely
         | different set of bazillion optional features making each
         | codebase look like an entirely different language from each
         | other.
         | 
         | I mean, this is kind of why C++ is successful. It's a toolbox
         | that'll do anything and you can write high level or low level
         | code.
         | 
         | > but maybe thats how we'll finally get rid of c++
         | 
         | Lol we're never getting rid of it. There's too much C++
         | software and will Rust replace it all? Probably not.
        
           | MathCodeLove wrote:
           | There's too much Flash content on the web we'll never
           | deprecate it
        
             | kllrnohj wrote:
             | If people actually got to make their own choice on that I'd
             | strongly guess that Flash wouldn't have ever gone away.
             | Browsers declared HTML5 was going to replace Flash _long_
             | before it was actually capable of doing so (and arguably
             | still isn 't).
             | 
             | But it only took ~4 "deciders" to kill Flash regardless of
             | what anyone else wanted, which was Apple, Microsoft,
             | Google, and Mozilla (and eventually Adobe decided it didn't
             | care about Flash anymore either). Nobody so centrally
             | "owns" C++ such that there could be a concerted deprecation
             | effort that anyone would actually care to listen to &
             | respect. Even if the C++ committee itself decided to kill
             | C++, and got G++, MSVC, and Clang on board, which is
             | extremely unlikely, would anyone even care that much or
             | just keep using the last release of the compilers with
             | support until the end of time? Kinda like they do for
             | FORTRAN. And COBOL. And etc...
        
             | not2b wrote:
             | Bad analogy. Flash was never the core technology of the
             | web, and it was proprietary to one company, which made it
             | much easier to kill when that one company lost interest. It
             | will take a long time for C++ to fade away, because of all
             | of the useful code written in it.
        
             | tialaramex wrote:
             | Right. And a lot of the content is on Myspace, which is
             | pretty much baked in to how everybody lives now. If you
             | tried to launch some sort of rival service, even if it was
             | as good as Myspace why would anybody join it? Next thing
             | somebody's going to talk about handheld computers again, as
             | if you could convince ordinary people to carry a computer
             | around with them. Not likely.
             | 
             | More Seriously: You shouldn't believe any of this nonsense
             | about how we're permanently locked in to something that's
             | less than a century old. There are plenty of people still
             | alive today who were born in a world that not only didn't
             | have C++ it didn't have programmable computers at all.
        
             | Mikeb85 wrote:
             | Flash was never that integral to the web... There was a
             | bunch of web plugins, flash was one of many... C/C++ is a
             | different level altogether.
        
             | josefx wrote:
             | And thanks to tools like Ruffle flash is still alive and
             | well, despite the coordinated effort to kill it.
        
         | dagmx wrote:
         | I've never met anyone who knows the entirety of C++ today. Some
         | people kept up with C++11 maybe, but anything beyond that is
         | quickly getting into the territory where there's bits of the
         | language that are truly arcane to even very experienced devs.
         | 
         | even then what does it mean to "know c++"? Do you mean the
         | spec? Specific implementations?
         | 
         | IMHO the language and implementation have been far from simple
         | for almost a decade now. I don't think that's necessarily a
         | negative though.
        
       | westurner wrote:
       | From https://discourse.llvm.org/t/rfc-lifetime-annotations-
       | for-c/... :
       | 
       | > _We are designing, implementing, and evaluating an attribute-
       | based annotation scheme for C++ that describes object lifetime
       | contracts. It allows relatively cheap, scalable, local static
       | analysis to find many common cases of heap-use-after-free and
       | stack-use-after-return bugs. It allows other static analysis
       | algorithms to be less conservative in their modeling of the C++
       | object graph and potential mutations done to it. Lifetime
       | annotations also enable better C++ /Rust and C++/Swift
       | interoperability._
       | 
       | > _This annotation scheme is inspired by Rust lifetimes, but it
       | is adapted to C++ so that it can be incrementally rolled out to
       | existing C++ codebases. Furthermore, the annotations can be
       | automatically added to an existing codebase by a tool that infers
       | the annotations based on the current behavior of each function's
       | implementation._
       | 
       | > _Clang has existing features for detecting lifetime bugs_ [...]
        
         | pornel wrote:
         | edit: Chrome evaluated previous [[clang::lifetimebound]], and
         | that wasn't enough:
         | 
         | https://docs.google.com/document/d/e/2PACX-1vRZr-HJcYmf2Y76D...
        
           | ameliaquining wrote:
           | Is this doc talking about the same feature that this post is
           | about? It sounds like the doc is talking about a different,
           | less-precise analysis that the post cites as prior art.
        
       | 323 wrote:
       | What happens if you make an annotation mistake? Could the
       | compiler generated code then have a security vulnerability, just
       | like when you use undefined behaviour and the compiler is then
       | allowed to optimize safety checks away?
        
         | Rusky wrote:
         | No- the idea is that if you make an annotation mistake, you
         | will get an error, because the program will not match its
         | annotations.
         | 
         | Lifetimes are not like an `unreachable` UB operation. Instead
         | they are just a description of cross-function information,
         | about what both the caller and callee are allowed to assume.
         | 
         | You could technically (both in Rust and under this proposal) do
         | the same checks without them, if you had access to the entire
         | program at once. However, this would be more expensive, and
         | probably give you less-localized error messages (a lot like C++
         | template errors, for similar reasons).
        
         | netheril96 wrote:
         | It doesn't affect code generation, only static analysis tools.
        
       | aaaaaaaaaaab wrote:
       | Nooo! If this gets implemented then Rust is basically killed...
       | :(
        
       | benreesman wrote:
       | I tend to think that the ROI for large legacy codebases is in
       | static analysis and instrumented tooling like the sanitizers.
       | 
       | I'm grudgingly coming around to the idea that Rust is probably a
       | rock-solid FFI story away from being a serious part of my kit
       | (pybind11 good, not like anything we have now).
       | 
       | But there is this in-between place where first-class
       | linear/affine typing could be bootstrapped off of move semantics.
        
         | kevincox wrote:
         | FWIW the Rust-C FFI is very solid. Binding to more complex
         | languages is in various degrees of progress. For C++ I have
         | heard really good things about https://cxx.rs/ (but never had
         | the need to try it). wasm_bindgen is already very good for
         | binding to JS and I have heard people having lots of success
         | writing Python and Ruby libraries in Rust (with some manual
         | glue on the scripted side).
        
         | imron wrote:
         | > Rust is probably a rock-solid FFI story away from being a
         | serious part of my kit (pybind11 good, not like anything we
         | have now).
         | 
         | Check out PyO3: https://github.com/PyO3/PyO3
        
       ___________________________________________________________________
       (page generated 2022-04-02 23:00 UTC)