[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)