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