Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Safe C++ (safecpp.org)
101 points by matt_d on Sept 13, 2024 | hide | past | favorite | 68 comments


Not a fan of where this is going. Just use Rust instead. All this is doing is making c++ even more unmanageable. When developers are pressed for time they will revert to what they are used to and know. What you get in the end is a mess of code with different ideas at different parts of the system because they are developed at different times by different people. Or you are going to put down very strict guidelines and redevelop everything according to these guidelines. Then you are back to rather use Rust instead. The guidelines is built in the Rust language.


Clearly, this is not for you.

I work in video games, most games use Unreal Engine, most existing toolsets and dependencies are built on C/C++, I have 17 years professional experience and nearly 30 years including hobbyist experience in C++. I often think I'd start new projects in Rust, but the last two large personal projects I started in C++, and one of them has been deployed in iOS, Android, Windows, Linux, Mac, WASM and RPi environments without much trouble. I haven't regretted that choice, even though I do really like Rust. In my contracting work, Rust just isn't an option. In my personal work, Rust is an option but since C++ has been getting better, I see less and less reason to use Rust.

But, you, please keep using Rust. It's a great language and I hope it someday eclipses C++. C++ was never designed as a cohesive language (Bjarne said exactly this in a talk I attended), it was designed as a grab bag of ideas, and I believe much of what's bad about C++ stems from that philosophy. Rust is built from the ground up with good ideas. But there's nothing wrong with improving C++ for those of us who it's really the best or only option.

EDIT: To be clear on this particular article, I agree it's not a great direction. But "just use Rust" is not helpful, and you may not have the problem this is trying to solve.


This is for corporations with 40 years of code in production, that hardly do greenfield development.

How will Rust look in 40 years, if it reaches thus far?

When will rustc stop depending on a C++ compiler construction framework?


This is pointless for corporations with 40 years of code in production.

Changing all those 40 years worth of code to the safe subset is equivalent to a total rewrite. Especially since usually the really old parts of the code have never been touched or modernized, so are usually C-like and therefore very far away from the safe subset.

One might as well rewrite it in something properly memory-safe.


I agree that this proposal isn't great. But also that you're not responding to what the comment is really saying - it's responding to the "just use rust" suggestion. If Rust is a good choice, great. But that doesn't mean we should abandon all attempts to improve standardization of C++. And I mean "attempts" - production conversation should be around why something doesn't work and why it fails, "just use rust" is not really relevant here. It's perfectly fine to say when starting a new project or figuring out how to rewrite an existing codebase, but in a conversation about C++ standards it's just not relevant.

I like Rust and very happy it's being used more and more. I am just tired of it constantly coming up in any talks about C++ on HN.


Especially when building the Rust compiler, at least for the time being, requires C++, and many domains Rust wants to play only have C and C++ based industry standards like Khronos APIs, CUDA, consoles,...


Those same corporations have made the transition from C to C++ during the 40 years.

They aren't going to do the same with a complete reboot.

It is the same reason why from all JavaScript wannabe replacements, including WebAssembly ones, Typescript is the one that got the price.


That transition from C to C++ isn't really the same kind of transition. When moving over to C++ from C, you get to keep and use all your old code and gain new capabilities for the newly-written parts. Extending stuff like this is easy.

Moving to a subset is very hard, because you loose capabilities. And since this is about memory-safety, you either loose them all at once, or you aren't really memory-safe until you migrated all your codebase to the subset.

So while C->C++ takes 40 years, you get benefits in pieces during the 40 years of migration. With C++->SafeC++, you can only get those benefits at the end of those 40 years. Which usually means that it won't be done at all.

What might be a possible approach is to divide a huge codebase into smaller parts, isolate them from each other (microservices if you want to call it that) and then migrate those smaller parts to SafeC++. But since that would change the whole architecture of the codebase, the viability is always a very big "maybe".


> you aren't really memory-safe until you migrated all your codebase to the subset.

That's true for every gradual migration, even to another language. At the limit any rust code that uses 'unsafe' is not really memory safe for small values of 'really'.

Perfect is the enemy of good. An incremental improvement to the status quo is still an improvement.

Although I'm firmly in the "I'll believe [the practicality] when I'll see it" camp, I'm all in favour of these experiments with memory safety.


It is still much easier to sell than throw away, rewrite in Rust, while the ecosystem is full of growing pains, and not yet taken seriously in many industry critical standards.


> When developers are pressed for time they will revert to what they are used to and know.

Is not being able to do this a great selling point for Rust? It's better to have nothing instead of code that may have a bug?


I agree, with the slightly altered advice: just use Swift instead


And ignore vast majority of computers? And suffer with Xcode?


Swift runs on windows and linux too. It’s not limited to Apple devices by any means.


I believe this is Circle C++ with a new domain name. See also:

https://news.ycombinator.com/item?id=40553709 - Circle C++ with memory safety (2024-06-02)

https://news.ycombinator.com/item?id=23086227 - Circle – A C++ compiler with compile-time imperative metaprogramming (2020-05-05)

https://news.ycombinator.com/item?id=19224176 - Circle: The C++ Automation Language (2019-02-22)


Circle is the work that feeds into this WG21 paper proposal, so naturally looks like Circle.


I think this is really interesting work, and as others have said it is a herculean undertaking by Sean Baxter to try to rework C++.

That being said, I'm not sure I like the direction. C++ is already more than complicated enough. And I'm not a big fan of the "mut XOR shared" principle taken from Rust, because I don't really have the problem of accidentially mutating something that was shared. Well, maybe I don't get the point because I'm a "Blub programmer" :-).

The example that is presented here and in many introductory articles about Rust is mutating a vector while iterating it. But why can't we just have a vector type that tolerates that and does something well-defined? What if we actually want to modify the list while iterating, because we are filtering the list for example?

I think one forward for C++ would be to define a safe subset. Deprecate a lot of features when using this subset, and make sure with a linter that they are not used. Hide raw pointers as much as possible. Ban UB as much as possible or give it some safe default. If you need some "UB" for optimisations (that is, you need the compiler to be able to make certain assumptions), then I want the compiler to tell me:

> "Could optimize further if we assume `a` and `b` are not aliasing, please add annotations to allow optimisation or to suppress this message".

Finally we need a new boring standard lib, closer to Java and Qt than the gnarlyness of the STL. The spirit should be like that of Java or Go, boring, efficient enough, productive for the average developer, not to many ways to shoot yourself in the foot.


“mut XOR shared” is less about protecting you from accidentally making inconsistent mutations, it’s more about the compiler’s need for a strong invariant against action-at-a-distance so it can manage to reason correctly about code in an isolated context.


> But why can't we just have a vector type that tolerates that and does something well-defined? What if we actually want to modify the list while iterating, because we are filtering the list for example?

This adds overhead that’s not always desired. And sometimes, this ends up being a logic bug. There are languages where this produces a result instead of being UB, but it’s almost always shown off as a “can you believe this wacky behavior” because it’s not intuitive. From a random search I just did: https://stackoverflow.com/questions/12232788/adding-to-an-ar...


I absolutely love this, not just because of the safety aspect but because of how expressive it seems to be.


It's nice, but it's about the tenth attempt to make C/C++ safe. I've been down that road myself. To succeed, this needs some major organization, something the size of Microsoft, Google, or the U.S. Government, pushing it hard. Someone has to fund retrofits of important trusted code, such as OpenSSL.

When you're all done, it will be at least as crufty as C++.


This is (or is going to be) a standard proposal I think, not just a dialect .

Of course it still need buy in from the major compilers to have a chance to be accepted.


There are competing "safe C++ syntax" projects being advocated by people on WG21 (as well as some members who seem to be opposed to any memory or concurrency safety tools beyond guidelines and linters). It's going to be up an uphill fight for any of them to gain traction, but at least some people are putting in the effort.


I think this one is the first one that actually got a document number assigned though, right? Although still a draft number and it is far from actually having wordings.


On the other hand you will be able all the nice game engines, if this goes through. :)

OpenSSL is C and I hardly believe they will ever adopt anything else.


Honestly, with the kind of changes and restrictions that you would end up needing, you'll end up with enough of a different language that you can't cleanly migrate a big existing codebase.

If a codebase wasn't designed with the intent of being able to prove validity & safety in the first place, trying to achieve it would be pretty rough. Not literally the same as trying to rewrite it in Rust, but the biggest problems would probably be comparable.


It is still easier than a full rewrite.

Adoption of C++, Objective-C over C, Typescript over JavaScript, versus alternatives that forced full rewrites, should be taken into account.

It is no accident that one of Swift's long term roadmaps, is having for C++ the same kind of transparent interop they leverage from clang with Objective-C.


> Exceptions are a poor way to signal out-of-memory states. If containers panicked on out-of-memory, we’d enormously reduce the cleanup paths in most functions.

What exactly is the difference between a panic and an exception? According to everything I've been able to read about Rust's panic mechanism, it's just that "panics shouldn't be used for normal errors", which is just a prescription on the normative use of the construct. A pretended difference. Can anyone clarify whether there actually is a real difference? Or when "Panic" is said here, does the author mean something that cannot be meaningfully recovered from? That would be a change most inappropriate!

One of the most prominent birthmarks that Rust was stamped with when it was born as an apps (not systems) language was its standard library being designed without regard to the possibility of reacting to out-of-memory conditions by anything other than aborting. Now that it's trying to pivot from solely an apps language to a systems language for some time, there seems to be work underway to correct that deficiency.

C++ for its part is not burdened with that mistake. There is remarkable attention paid in the STL to the possibility of an out-of-memory condition and allowing a complete recovery from the same. This is a useful feature that systems software often requires.


Rust was invented with an elevator firmware use cases in mind. The inventor happened to work for Mozilla, so it got traction in the lower level parts of Firefox next.

https://www.techspot.com/news/97654-how-broken-elevator-led-...

It has explicit error handling and no exceptions. All mission critical C++ software I’ve worked on uses --f-no-exception and panics then restarts on out of memory.

Hardware errors were way more common than OOMs for those embedded code bases, and we didn’t have the testing budget to fault inject OOMs everywhere in simulation.

I know other fields still try to use the “handle all error codes” approach, but “let it fail then restart” is simpler and (in my experience and theoretically) easier to get to be reliable, especially in distributed environments.

This isn’t a new idea. For instance, during the apollo 11 landing, the guidance computer was crash looping, but managed to recover the trajectory enough on each loop to get to the ground. Look at the section about 1201 and 1202 alarms here:

https://en.m.wikipedia.org/wiki/Apollo_Guidance_Computer


> Rust was invented with an elevator firmware use cases in mind. The inventor happened to work for Mozilla, so it got traction in the lower level parts of Firefox next.

TechSpot is referring (without attribution) to an interview the MIT Technology Review did with Hoare, and tacked on a claim that Rust was invented "to prevent issues such as his elevator breaking down" that isn't there in the source material. Elevator firmware is not systems software in any case, and the Rust manual Hoare authored states:

> Rust's goals arise from the context of writing large programs that interact with the internet -- both servers and clients -- and are thus much more concerned with safety and concurrency than older generations of program.

https://github.com/graydon/rust-prehistory/blob/master/doc/r...

The language described there relies on a much larger runtime than Rust today does. It is no systems language. Later it even grew things like the @ garbage-collected pointers.

Those would eventually go away as it turned out that people writing apps don't want to think about memory at all (and are better served by using a real apps language) and Rust instead started embracing its potential as a systems language:

> Although Rust didn't start out that way, one of the most interesting applications of Rust has been very low-level programming, even down to the level of kernels. The interest in this application of Rust was something of a surprise to us, but in hindsight it makes perfect sense. Low-level control over memory management isn't something that most applications software, especially on the server side, wants; most of that software has migrated over to languages like Java, Ruby, and JavaScript that trade control and performance for convenience by making memory management automatically, and dynamically, managed by the runtime. The remaining class of software, most of which is written in C and C++, is software that must manage memory manually in order to achieve some combination of performance, simplicity, and/or the ability to self-host. The prospect of using a new language for this class of software, which includes OS kernels, game engines, and browser engines among others, is what is fueling the growth of the nascent Rust community.

https://pcwalton.github.io/_posts/2013-06-02-removing-garbag...

For a lot of systems software, and I am thinking of system service managers ("init systems") in particular, failing and restarting is not viable. OOM conditions must be recoverable. Rust has recognised this and is trying to correct the course they went down in its early days of ignoring this, being unimportant for an apps language, but vital for a systems language.

One feature they have taken to is throwing panics on OOM conditions:

https://github.com/rust-lang/rust/issues/43596

These "panics" can unwind the stack and be caught with panic::catch_unwind, but with the proviso of only "sometimes". If you take that at face value, then it's not a very useful feature. The implication in the issue linked above however is that this could become a viable way to recover from OOM conditions. And the mechanism proposed there is indistinguishable from exceptions other than that it's insisted that it has nothing to do with exceptions and that you can't rely on it catching panics for you.


> What exactly is the difference between a panic and an exception?

By my understanding it's the difference between what you want to do and how you do it. Panicking is the what ("Something went wrong! Halt operation!"), exceptions are one of the hows (unwind). Aborting is the other one.

That's one of the reasons panics should not be used for normal errors - there's no general guarantee you can catch them. Granted, that's more of a restriction on library developers, since they don't control the panicking mode.

> C++ for its part is not burdened with that mistake. There is remarkable attention paid in the STL to the possibility of an out-of-memory condition and allowing a complete recovery from the same. This is a useful feature that systems software often requires.

At least by my impression OOM in the STL is generally handled via std::bad_alloc. While that's something at least, I'm not sure I'd call that "remarkable attention" on its own, especially since I don't think it's that uncommon for systems software to be compiled with -fno-exceptions. I'm not sure using alternative allocators would help in this case either, since I don't think the way APIs are defined allow you to surface allocation failures in any way other than exceptions (e.g., what happens on OOM during std::vector::push_back?).

To me, "remarkable attention" would be more akin to what Zig does with allocator parameters and surfacing allocation errors in the API.


why isn't this getting traction? it's a pretty big deal. it's the most safe of proposed C++ safety extensions by far


It's not that it isn't gaining traction. Rather it's just a massive, herculean undertaking that Sean and co have been working on in the background. Hell Sean only just a few months ago started posting demos on twitter of their C++ borrow checker proof of concept finally working in moderately non-trivial examples.

This isn't to say that their progress isn't going well. Rather they've been making incredible progress at a rate nobody really thought possible but this is just a truly Herculean effort.

Also this proposal is literally only 2 days old. The work has been in progress for years but this is to my knowledge the first time it has actually been proposed formally.


This is correct. Sean has been working for something like two years implementing these ideas to make sure they’re feasible, and only recently turned to actual advocacy for them, now that the experiment appears successful.


Do you see this realistically getting implemented?


It is implemented in Circle already.


Oh wow I just took a look and it’s really impressive. Adding more complexity to C++ does make it just that much more difficult for beginners though


Because C++ is big and old and everyone wants it to be something different. Functional programmers want it to be more functional, OOP developers want more object oriented, etc. And the syntax must look familiar. And everything must be backwards-compatible and all older legacy code needs to work fine. And it must have next-to-no runtime cost. Etc.

Making any changes to C++ is a slow process - as it is for any older and larger language.

Plus...I think memory safety is a pretty low priority area for C++, frankly. It's harder to shoehorn in at this point, C++ has never prioritized it over raw performance or flexibility.


Well, back in the pre C++98 days, compiler provided frameworks used to be safer by default than what got standardised.


There's no point. Rust can already be added incrementally in an existing C++ or C project. Like how Servo was in Firefox, or Rust in the Linux kernel.


Why was this downvoted? It's not just another generic "rewrite it in Rust" comment. I was making a specific point that incremental rewrites of existing C++ code are very possible in Rust. And that process would not be that different from rewriting the code in a "safe" dialect of C++.

If it makes you feel better you can rename the Rust language to "C++2.0" but the rewrite process itself will be the same in both cases. You keep expanding the amount of safe code you have within the codebase over time, one module at a time.


Your first sentence comes across as incredibly combative, and not everyone agrees. Personally, that's why I downvoted it. It would be a much better comment if it was the same thing without it.

For example, one advantage of this strategy as opposed to an incremental Rust re-write is that you would retain a single toolchain, whereas that would require having two.


Is it actually a single toolchain? I was not clear on that from reading the article.

Anyway, all I can say is that having used both C++ and Rust for many years, I have zero desire to use another C++ toolchain.


Yes, it's an extension to C++, so as it is implemented now in Circle, and as it would be if it were to be standardized, it would be part of the regular toolchain.


If it would work, it would gain traction. Guaranteed.


C++ is a very slow moving language. There are various rumblings about making it memory safe, but if we get a proper memory safe C++ it'll probably be in the '30s.


It just looks like an ugly version of rust.


Considering that Rust is an ugly language to read, it shouldn't make much difference.


What do you think is beautiful about Rust, and why?

How do you think this proposal's equivalents are uglier?


Wait, there's a beautiful version?!


I love Rust but it didn't invent memory safety. This is a weird take


Your parent didn’t suggest that it did. But Sean is very clear that these ideas were inspired by Rust.


Jean-Yves Girard


Coming into this thread late, but one thing I wished to see in this discussion is the mention of the truism that I first read here:

Successor languages must have full (or at least broad) compatibility with the language they're replacing. Look at C++ itself; it started as a C transpiler, and has some of the longest running C compat of any extant language. Same goes for TypeScript, which is fairly widely used in production, I think even moreseo than Rust, for all the hype it generates,

This is an interesting proposal for that reason alone; it would allow a slow and deliberate conversion process, but also would allow safety standards to include language like "no unsafe blocks allowed unless proven safe"


Aside from "making C++ safe", the design looks like a good opportunity to have better Rust interop, if a standardized FFI with clear object lifetime semantics can be established between C++ and Rust, it's a win for everyone.


Or the "other" languages

Zig/Nim/D/Go (or even scripting languages R, Julia, *Lisp, TCL, Ruby, Scilab, Python ...)

at last.


Also add Ada... we tend to forget Ada but it was doing what Rust is before it was cool.


>> we tend to forget Ada but it was doing what Rust is before it was cool.

As someone who has done a fair bit of Rust and Ada programming, this is not quite accurate an overlooks the innovations of Rust and the strengths of Ada:

In my opinion, Rust's biggest innovations are 1) borrow checking and "mutation XOR sharing" built into the language, effectively removing the need for manual memory management or garbage collection, 2) Async/Await in a low-level systems language, and 3) Superb tooling via cargo, clippy, built-in unit tests, and the crates ecosystem (in a systems programming language!) Rust may not have been the first with these features, but it did make them popular together in a way that works amazingly well.

Ada's strengths are its 1) powerful type system (custom integer types, use of any enumerated type as an index, etc.), 2) perfect fit for embedded programming with representation clauses, the real-time systems annex, and the high integrity systems annex, and 3) built-in Design-by-Contract preconditions, postconditions, and invariants. Compared to Rust, Ada feels a bit clunky and the tooling varies greatly from one Ada implementation to another. However, for some work, Ada is the only choice because Rust does not have sufficently qualified toolchains yet. (Hopefully soon . . .)

Both languages have great foreign function interfaces and are relatively easy to use with C compared to some other programming languages. But Ada is not "Rust but from the 1980s" anymore than Rust is "like Ada, but newer".

Having done a fair bit of C programming in the past, today I would always choose Rust over C or C++ when given the choice.

But when working with really big C++ codebases, you don't always get to choose so something like Safe C++ is good to help add Rust-like features to C++ code.


Ada Spark goes beyond safety guarantees of Rust. However how many here want to prove their invariants hold for their UI?


You don’t have to verify your whole application, just set SPARK_Mode => Off where you want to skip it. Alternatively, set the global default to Off and it becomes an opt-in feature.

Proof levels depend on your goals, but most requirements are satisfied by proof of “Absence of Runtime Exceptions” (AoRTE), which is easier than a full formal proof.


How would you prove stuff about your code like memory safety.


Out of bounds access would raise a runtime exception, so absence of runtime exceptions proves that cannot happen. Recent versions of SPARK add functionality similar to Rust’s borrow checker as well.

You can find more information in the users guide. https://docs.adacore.com/spark2014-docs/html/ug/index.html


Even PHP is a perfectly fine, memory-safe language.


This looks like a huge improvement from the original Circle language that didn't look like C++ at all. I love it and would love to have it in C++.


Even without all the safety parts, this is amazing:

> The new choice type is a type safe discriminated union. It’s equivalent to Rust’s enum type.


"safety" != "memory safety"

A formally proven implementation could be called "safe".

But code written with checked arrays and without explicit pointers isn't magically safe, and promoting a language or a cookbook as something that makes applications "safe" is just abusive and annoying marketing.


Considering my city Is still running 16 bit Operating systems Off floppy drives For some of the infrastructure... Well I don't know, Maybe that makes it more secure? I have no idea How These systems haven't been hit yet. Or maybe they have?


"Over the past two years,"

suddenly they remembered to make it safe!!!

Seems others used their backdoors.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: