Not sure what to make of this. Is this a feature, or a whole design?
The idea here is simply that if the whole call tree can be examined, rather than only looking at one module at a time, less annotation is needed. That's reasonable enough, although it may result in long compile times.
What I don't see here is:
- How are circular references handled?
- C++ objects implicitly have links from parent to child and child to parent. So there's built-in circularity. How does that work?
- What about interior mutability?
Examining the whole call tree is useful. It allows catching mutex deadlocks where a thread locks against itself and stalls. It may be possible to replace something like Rust's RefCell with something, perhaps "SafeCell", that supports the same .borrow() and .borrow_mut operations, checking them at compile time for overlapping borrow scopes.
> Not sure what to make of this. Is this a feature, or a whole design?
My (high-level) understanding is that this is a response to some feedback [1, 2] to the author's previous paper [0] which (very generally speaking) adds some Rust-like systems/semantics to C++ to achieve memory safety. Some commenters voiced disapproval of the need for lifetime annotations and expressed a desire to find a solution that doesn't require them. I think this paper is an exploration of that idea to see what can be achieved without lifetime annotations.
Well, there is: it's easy to trigger unsafe behaviour by calling a virtual method accessing a child class data member (say some std::vector) when in the parent class destructor, as the child sub object will already have been destroyed. Thus it's reasonable to say that there is a bidirectional ownership relationship.
Yes. Rust has safe destructor semantics. C++ does not.
Back references are really hard in Rust. You can do them with Rc, RefCell, and Weak, but there's a lot of run-time checking involved. More static analysis could eliminate most of that run-time checking. .borrow(), .borrow_mut(), and .upgrade().unwrap() panic if they ever fail. So you really want to prove they can't fail. If you can do that, you don't need the run-time checks. In that direction lies the solution to Rust's limited data structure problems.
When in the parent class destructor, the v-table will point toward the parent's v-table not the child's (try it out), so I'm not sure how you'd call a virtual method accessing a child member.
Would it? Or is it undefined? No C++ runtime I know of will generate a new, different vtbl just to switch it out when invoking the non-owning base class destructor on an object. Nor am I familiar with any part of the C++ stnadrd that mandates this behaviour, or even mandates vtbls.
It doesn't create a new vtbl, when invoking the base object destructor it just repoints the current vtable ptr to the base object vtable as first thing:
https://godbolt.org/z/MKx6oTE63
I can't recite chapter and verse, but I'm pretty sure that's mandated by the standard and it mirrors the behaviour in the constructor.
Whole new design which Sean Baxter suggests the committee foist upon C++. Being a continuation of "safe C++", it seems to be part of coping with the challenge of the popularity of Rust - and perhaps even more with how some lobbyists have gotten the US government to adopt some decisions against using "memory-unsafe languages".
Just remember that WG21 papers are their authors' position/claims, not the committee's or the community's, unless accepted. I think, or rather hope, this is not accepted.
> This proposal implements two sets of constraint rules. Free functions constrain return references by the shortest of the argument lifetimes. Non-static member functions constrain return references by the implicit object lifetime.
It seems like this proposal just chooses a general set of constraints and forces it everywhere? Under these rules you couldn't e.g., have a regex object with a match method that takes a string and returns a reference to that same string. Seems too limiting to be practical.
The draft for this paper was submitted a few days ago at https://news.ycombinator.com/item?id=41850258 (3 points, no comments). Not sure if there are any differences between the two versions.
Overall interesting read. I'm rooting for Sean. If he has faith that C++ is not too far gone to be saved...perhaps I can, too.
> Explain the infeasibility of making legacy lvalue- and rvalue-references memory safe.
> Relocation must replace move semantics
He advocates for adding lots of things to the language, but that point is a massive change to the language IMO. I definitely think move semantics are awful; as is the hierarchy of rvalues (xvalues and glvalues) is nuts, but at this point can they be removed from the language? Or do I misinterpret the point? Perhaps this is different between unsafe blocks (vs default safe blocks)?
If you check the flames on Reddit, not sure if he will keep at it, the WG21 feedback has been pretty disapointing, hence this paper, as kind of response to those replies.
Most folks at WG21 think profiles will magically solve the problem, even though many of the ideas are only available on paper, not a random C++ compiler.
The biggest issue is that while a language like Ada also has profiles, the safety culture is much different, and they are part of the ecosystem since Ada83, not something that was added later.
It is a gargantuan endeavour tbh. I am writing safety C++ code for a living and still am undecided if it is a good idea to continue to do so. Safety benefits a lot from understandability and that is undoubtedly better with simple code and a simple(r) language.
Trying to keep the legacy code alive through compatibility introduces more than two languages in one, as all the combinations must be understood as well. Wrapping legacy code into bigger "unsafe" blocks and guarding the perimeter of that block is not a bad idea, it creates less mix to reason about.
Would allowing only safe references in safety code really hinder adoption?
The folks talking about profiles and the folks talking about rust-like semantics are speaking past each other. They're not solutions to the same problem. Profiles is essentially "let's formalize the 80% cases of easy static analysis rules and deal with the hard stuff later". It doesn't get you memory safety in the sense of rust where it's safe if it compiles (modulo unsafe).
Author’s paper also doesn’t solve the problem, because I wager most C++ programmers wouldn’t touch this rustified C++, which manages to look even worse than the already not light on the eyes original.
Probably, but then they will need to decide how much they feel like if C++ joins the computing equivalent of hazardous goods, where products written on it only have clearance for specific use cases.
See "Product Security Bad Practices" from CISA and FBI, published last week.
=> "Software manufacturers should build products in a manner that systematically prevents the introduction of memory safety vulnerabilities, such as by using a memory safe language or hardware capabilities that prevent memory safety vulnerabilities. Additionally, software manufacturers should publish a memory safety roadmap by January 1, 2026."
> such as by using a memory safe language or hardware capabilities that prevent memory safety vulnerabilities
Heh, now I do believe that my partially-memory-corruption-based side job may be in danger. If it was just "by using a memory safe language" I do not care, but if they want to boost hardware assisted mitigations (better and ubiquitous memory tagging etc) it's going to have significant impact.
They are advocating for a holistic approach for safety. But of course, when talking about a specific part of that, they’ll talk about the specifics. With regards to programming languages, memory safety is the next big thing to tackle.
Ironically some Turing Awards could already see this coming in 1980, but it was needed some money to be tied to CVE's, to make this actually matter.
"A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980 language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
-- C.A.R Hoare's "The 1980 ACM Turing Award Lecture"
It is only taking a couple of decades to get there.
By the way I know you already are aware of this, more for those that don't.
> It is only taking a couple of decades to get there.
I don't know how to break it to you, but the Eighties were 40 years ago :(
In any case I was wondering if Hoare, in addition to bound checkings, felt as strongly about the so called temporal safety, but his words are unambiguous: he is not just rejecting any form of Undefined Behaviour, he wants anything that passes static checking to have useful valid semantics, reminiscent of the "well typed programs can't go wrong" maxim.
A side effect of native language, where a couple doesn't translate to 2, rather some.
That maximum is usually the approach to UB in sane systems languages, literally meaning undefined and that is it, possibly having traps or similar enabled by default.
It isn't the wildcard for any kind of optimisations are allowed, aka "please go wild dear optimiser".
Yeah I know, too many folks at WG21 think is this going to be just like Ada so they are on the clear.
Except that back then, it mostly failed due to the prices of compilers adjusted to goverment level contracts, the few UNIX vendors like Sun that sold such compilers it was extra not part of the regular UNIX SDK, the cost of hardware to run modern Ada compilers (Rational started as a Ada Machine company), so it was dropped as requirement, and thus stuff like JPL, MISRA, AUTOSAR, F-35 C++ (what a success this one),... that allowed cheaper development with guiderails.
Now that exploits have money placed on them, in fixing CVEs, pushing fixes into devices, insurances paying for downtime,.... goverments and companies burning that money, have reached the conclusion that software is now critical infrastructure, and must be dealt accordingly like everything else that is criticial in modern societies.
This is not going to be Ada again, as much some folks wish for.
Looking forward for all the government sanctioned "safe" software that continues to send real time telemetetry while it tries to manipulate me into spending money on things I don't need. But at least only the good guys will be able to do that, yay.
C++ isn't exactly famous for being readable to begin with.
If the name Sean Baxter doesn't ring a bell, he's creating the Circle compiler which one step after another fixes the problems of C++ (while not trailing off into a completely new language):
> which one step after another fixes the problems of C++
Somehow, multiple groups of people have recently decided they want to "fix the problem": Carbon, Circle, CppFront2. But apparently, it's different fixes, and the formulation of the "the problem" is different.
I am wary of rushing to do this, considering the fate of the D, a previous attempt to "fix the problem" with C++.
AFAIK the big difference of Circle to both Carbon and CppFront2 is that Circle is (mostly?) a superset of C++17, so it doesn't change the basic syntax, just adds things on top.
I don't care much either way tbh, just watching from the sidelines while happily churning out C and Zig code ;)
But IMHO Sean Baxter does more for C++ than the entire committee ever did in the last three decades - if C++ would have moved into a similar direction I probably wouldn't have dropped it as my main language).
An annoying but rather valid quip by Bjarne Stroustrup is that "there are only two kinds of languages: the ones people complain about, and the ones nobody uses." D has, statistically, not been attactive enough for people to switch to it from C++. Perhaps this is unfortuante... ? I wouldn't mind a link to an argument in favor of switching to (today's) D from (today's) C++.
It may be done on purpose? To me this looks like a satirical piece in response to some "nooooo how dare you introduce lifetime annotations to C++"-style comments on the same author (Sean Baxter)'s C++ lifetime annotation proposal.
Seeing this on HN feels a bit like those apocryphal stories of the professor who hands out the "read all the questions before you start" quiz, and the last question just tells the students to ignore everything else, wait 30 minutes and leave for full marks.
Not sure what to make of this. Is this a feature, or a whole design?
The idea here is simply that if the whole call tree can be examined, rather than only looking at one module at a time, less annotation is needed. That's reasonable enough, although it may result in long compile times.
What I don't see here is:
- How are circular references handled?
- C++ objects implicitly have links from parent to child and child to parent. So there's built-in circularity. How does that work?
- What about interior mutability?
Examining the whole call tree is useful. It allows catching mutex deadlocks where a thread locks against itself and stalls. It may be possible to replace something like Rust's RefCell with something, perhaps "SafeCell", that supports the same .borrow() and .borrow_mut operations, checking them at compile time for overlapping borrow scopes.
> Not sure what to make of this. Is this a feature, or a whole design?
My (high-level) understanding is that this is a response to some feedback [1, 2] to the author's previous paper [0] which (very generally speaking) adds some Rust-like systems/semantics to C++ to achieve memory safety. Some commenters voiced disapproval of the need for lifetime annotations and expressed a desire to find a solution that doesn't require them. I think this paper is an exploration of that idea to see what can be achieved without lifetime annotations.
[0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p34...
[1]: https://old.reddit.com/r/cpp/comments/1ffgz49/safe_c_languag...
[2]: https://old.reddit.com/r/cpp/comments/1fiuhb7/the_empire_of_...
> - C++ objects implicitly have links from parent to child and child to parent. So there's built-in circularity. How does that work?
Can you elaborate? In my understand, the parent/child relation are on the class not the object itself.
A child class can reference fields in the parent, and the parent can call virtual functions in the child.
You mean vtable for inheritance? There's no ownership relation here.
Well, there is: it's easy to trigger unsafe behaviour by calling a virtual method accessing a child class data member (say some std::vector) when in the parent class destructor, as the child sub object will already have been destroyed. Thus it's reasonable to say that there is a bidirectional ownership relationship.
Yes. Rust has safe destructor semantics. C++ does not.
Back references are really hard in Rust. You can do them with Rc, RefCell, and Weak, but there's a lot of run-time checking involved. More static analysis could eliminate most of that run-time checking. .borrow(), .borrow_mut(), and .upgrade().unwrap() panic if they ever fail. So you really want to prove they can't fail. If you can do that, you don't need the run-time checks. In that direction lies the solution to Rust's limited data structure problems.
When in the parent class destructor, the v-table will point toward the parent's v-table not the child's (try it out), so I'm not sure how you'd call a virtual method accessing a child member.
Would it? Or is it undefined? No C++ runtime I know of will generate a new, different vtbl just to switch it out when invoking the non-owning base class destructor on an object. Nor am I familiar with any part of the C++ stnadrd that mandates this behaviour, or even mandates vtbls.
It doesn't create a new vtbl, when invoking the base object destructor it just repoints the current vtable ptr to the base object vtable as first thing:
I can't recite chapter and verse, but I'm pretty sure that's mandated by the standard and it mirrors the behaviour in the constructor.Or you meant something else?
Of course it would, not a lot would run otherwise. Here is very nice writeup about vtables and constructors: https://shaharmike.com/cpp/vtable-part4/
Section 11.9.5.4 of the C++23 standard describes the observable behaviour.
Whole new design which Sean Baxter suggests the committee foist upon C++. Being a continuation of "safe C++", it seems to be part of coping with the challenge of the popularity of Rust - and perhaps even more with how some lobbyists have gotten the US government to adopt some decisions against using "memory-unsafe languages".
Just remember that WG21 papers are their authors' position/claims, not the committee's or the community's, unless accepted. I think, or rather hope, this is not accepted.
> This proposal implements two sets of constraint rules. Free functions constrain return references by the shortest of the argument lifetimes. Non-static member functions constrain return references by the implicit object lifetime.
It seems like this proposal just chooses a general set of constraints and forces it everywhere? Under these rules you couldn't e.g., have a regex object with a match method that takes a string and returns a reference to that same string. Seems too limiting to be practical.
No, it explores this particular design, but concludes that lifetime annotations are needed.
The draft for this paper was submitted a few days ago at https://news.ycombinator.com/item?id=41850258 (3 points, no comments). Not sure if there are any differences between the two versions.
Overall interesting read. I'm rooting for Sean. If he has faith that C++ is not too far gone to be saved...perhaps I can, too.
> Explain the infeasibility of making legacy lvalue- and rvalue-references memory safe.
> Relocation must replace move semantics
He advocates for adding lots of things to the language, but that point is a massive change to the language IMO. I definitely think move semantics are awful; as is the hierarchy of rvalues (xvalues and glvalues) is nuts, but at this point can they be removed from the language? Or do I misinterpret the point? Perhaps this is different between unsafe blocks (vs default safe blocks)?
If you check the flames on Reddit, not sure if he will keep at it, the WG21 feedback has been pretty disapointing, hence this paper, as kind of response to those replies.
Most folks at WG21 think profiles will magically solve the problem, even though many of the ideas are only available on paper, not a random C++ compiler.
The biggest issue is that while a language like Ada also has profiles, the safety culture is much different, and they are part of the ecosystem since Ada83, not something that was added later.
It is a gargantuan endeavour tbh. I am writing safety C++ code for a living and still am undecided if it is a good idea to continue to do so. Safety benefits a lot from understandability and that is undoubtedly better with simple code and a simple(r) language.
Trying to keep the legacy code alive through compatibility introduces more than two languages in one, as all the combinations must be understood as well. Wrapping legacy code into bigger "unsafe" blocks and guarding the perimeter of that block is not a bad idea, it creates less mix to reason about.
Would allowing only safe references in safety code really hinder adoption?
The folks talking about profiles and the folks talking about rust-like semantics are speaking past each other. They're not solutions to the same problem. Profiles is essentially "let's formalize the 80% cases of easy static analysis rules and deal with the hard stuff later". It doesn't get you memory safety in the sense of rust where it's safe if it compiles (modulo unsafe).
Author’s paper also doesn’t solve the problem, because I wager most C++ programmers wouldn’t touch this rustified C++, which manages to look even worse than the already not light on the eyes original.
Probably, but then they will need to decide how much they feel like if C++ joins the computing equivalent of hazardous goods, where products written on it only have clearance for specific use cases.
See "Product Security Bad Practices" from CISA and FBI, published last week.
https://www.cisa.gov/resources-tools/resources/product-secur...
=> "Software manufacturers should build products in a manner that systematically prevents the introduction of memory safety vulnerabilities, such as by using a memory safe language or hardware capabilities that prevent memory safety vulnerabilities. Additionally, software manufacturers should publish a memory safety roadmap by January 1, 2026."
> such as by using a memory safe language or hardware capabilities that prevent memory safety vulnerabilities
Heh, now I do believe that my partially-memory-corruption-based side job may be in danger. If it was just "by using a memory safe language" I do not care, but if they want to boost hardware assisted mitigations (better and ubiquitous memory tagging etc) it's going to have significant impact.
They are advocating for a holistic approach for safety. But of course, when talking about a specific part of that, they’ll talk about the specifics. With regards to programming languages, memory safety is the next big thing to tackle.
Ironically some Turing Awards could already see this coming in 1980, but it was needed some money to be tied to CVE's, to make this actually matter.
"A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980 language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
-- C.A.R Hoare's "The 1980 ACM Turing Award Lecture"
It is only taking a couple of decades to get there.
By the way I know you already are aware of this, more for those that don't.
> It is only taking a couple of decades to get there.
I don't know how to break it to you, but the Eighties were 40 years ago :(
In any case I was wondering if Hoare, in addition to bound checkings, felt as strongly about the so called temporal safety, but his words are unambiguous: he is not just rejecting any form of Undefined Behaviour, he wants anything that passes static checking to have useful valid semantics, reminiscent of the "well typed programs can't go wrong" maxim.
A side effect of native language, where a couple doesn't translate to 2, rather some.
That maximum is usually the approach to UB in sane systems languages, literally meaning undefined and that is it, possibly having traps or similar enabled by default.
It isn't the wildcard for any kind of optimisations are allowed, aka "please go wild dear optimiser".
We don’t know if this is a case of the government watchdogs barking while the caravan moves on.
Yeah I know, too many folks at WG21 think is this going to be just like Ada so they are on the clear.
Except that back then, it mostly failed due to the prices of compilers adjusted to goverment level contracts, the few UNIX vendors like Sun that sold such compilers it was extra not part of the regular UNIX SDK, the cost of hardware to run modern Ada compilers (Rational started as a Ada Machine company), so it was dropped as requirement, and thus stuff like JPL, MISRA, AUTOSAR, F-35 C++ (what a success this one),... that allowed cheaper development with guiderails.
Now that exploits have money placed on them, in fixing CVEs, pushing fixes into devices, insurances paying for downtime,.... goverments and companies burning that money, have reached the conclusion that software is now critical infrastructure, and must be dealt accordingly like everything else that is criticial in modern societies.
This is not going to be Ada again, as much some folks wish for.
Looking forward for all the government sanctioned "safe" software that continues to send real time telemetetry while it tries to manipulate me into spending money on things I don't need. But at least only the good guys will be able to do that, yay.
This appears to be a out of season April Fool's joke.
Am I the only one who thinks the this sacrifices the readability?
C++ isn't exactly famous for being readable to begin with.
If the name Sean Baxter doesn't ring a bell, he's creating the Circle compiler which one step after another fixes the problems of C++ (while not trailing off into a completely new language):
https://www.circle-lang.org/quickref.html
> which one step after another fixes the problems of C++
Somehow, multiple groups of people have recently decided they want to "fix the problem": Carbon, Circle, CppFront2. But apparently, it's different fixes, and the formulation of the "the problem" is different.
I am wary of rushing to do this, considering the fate of the D, a previous attempt to "fix the problem" with C++.
AFAIK the big difference of Circle to both Carbon and CppFront2 is that Circle is (mostly?) a superset of C++17, so it doesn't change the basic syntax, just adds things on top.
I don't care much either way tbh, just watching from the sidelines while happily churning out C and Zig code ;)
But IMHO Sean Baxter does more for C++ than the entire committee ever did in the last three decades - if C++ would have moved into a similar direction I probably wouldn't have dropped it as my main language).
It fixed the problem with C++ and in an exceptional way.
Now with @live, even when GC is on, they are ahead of Rust.
I love it.
An annoying but rather valid quip by Bjarne Stroustrup is that "there are only two kinds of languages: the ones people complain about, and the ones nobody uses." D has, statistically, not been attactive enough for people to switch to it from C++. Perhaps this is unfortuante... ? I wouldn't mind a link to an argument in favor of switching to (today's) D from (today's) C++.
The examples in the proposal already look like a completely new language. And yes, one that is significantly less readable than C++.
It may be done on purpose? To me this looks like a satirical piece in response to some "nooooo how dare you introduce lifetime annotations to C++"-style comments on the same author (Sean Baxter)'s C++ lifetime annotation proposal.
On the other hand its published on open-std.org which adds to its credibility
I'm not a fan of Rust, but my God, just use Rust instead of this. This is unreadable.
[flagged]
Seeing this on HN feels a bit like those apocryphal stories of the professor who hands out the "read all the questions before you start" quiz, and the last question just tells the students to ignore everything else, wait 30 minutes and leave for full marks.