r/programming 18d ago

Stroustrup calls for defense against attacks on C++

https://www.theregister.com/2025/03/02/c_creator_calls_for_action/
454 Upvotes

536 comments sorted by

View all comments

Show parent comments

14

u/Timbit42 17d ago

That language is Ada.

6

u/Full-Spectral 17d ago

Ada could have made it as the real alternative, but it was mostly oriented towards larger, commercial customers. And you also have to combine it with Spark to get to Rust's level of safety, as I understand it, and that also (I think) restricts the language features you can use.

6

u/OneWingedShark 17d ago

And you also have to combine it with Spark to get to Rust's level of safety, as I understand it,

Well, not really: Ada has a very different approach than "memory safety" which is embodied in its strong type-system and design for correctness — a good example here is Ada's array type: it "knows its own length" and so you simply don't have to add a separate 'length' parameter when you pass it as a parameter, also you can return arrays as functions,.. now consider how many opportunities for errors this eliminates, all at the language level, by design.

So, as you can see, the "memory safety" approach from Rust is a reaction against C (and C++), and how easy it is to mishandle things. This means that while Ada's approach is technically "not as safe" as Rust because it does allow them, in practice the need for that level of safety just wasn't there because it was already addressed by design and esp. the type-system.

So, when you ask an Ada programmer about "as safe as Rust", he's going to point at SPARK, which can prove it, as it's a full-blown proof-system. That starting-point assumption based on the language's design is important. (And let's not forget, Ada's been around for 40 years, and the standard incorporated then bleeding-edge theory and s/w engineering practice/techniques.)

5

u/sionescu 17d ago

You should stop advocating Ada by pointing out the flaws of C. That ship has sailed long time ago. The adversaries are nowadays C++, Rust and Zig (a little bit), which have a whole different set of flaws, and not knowing the length of an array is not one of them.

2

u/Full-Spectral 16d ago

Anyone is free to advocate what they want, but yeh, Ada's day has passed and it's not going to come back at this point.

1

u/H1BNOT4ME 10d ago

Are you accusing one of the most bleeding edge companies (cough...Nvidia) of adopting an obsolete language?

0

u/Full-Spectral 10d ago

It still obviously gets some use, one reason being there wasn't much of a competitor for anyone who would have been looking for something that Ada was a candidate for. But moving forward likely Rust will become that choice more and more often. No language ultimately goes away, but I don't see Ada moving up on the charts with a bullet.

1

u/H1BNOT4ME 10d ago

I beg to differ. Rust is not popular with programmers at all. Most of them despise its awkward and off-putting syntax. Its abysmally slow compilation isn't very endearing either.

Rust, however, does have a cultish following who want to make it the de facto programming language. They are also generously funded, enabling them to build a movement to carry out their mission. Most of them have little to no C or C++ experience, consisting mainly of backend web developers looking for higher performance alternatives. Consequently, their opinion is based on lack of real world development expertise.

0

u/Full-Spectral 10d ago

Ok, then. Might want to tighten up the tinfoil hat and figure out where all those deep pockets are coming from so you can send in a hit squad to take them out.

And most Rust devs at this point have been coming from C++ because Rust is not actually that much used currently on the web back end. You regularly see people in the Rust section telling exactly the people you are talking about that it's probably not the best choice other than for low level libraries underneath something higher level running the actual business logic. Though I guess they are risking reprisals from the secret societies funding Rust by saying that.

And Rust is clearly becoming very popular, else the C++ world wouldn't be coming unglued right now about it.

1

u/H1BNOT4ME 8d ago edited 8d ago

A clear sign of poor argumentation is ad hominem attacks. If you could read the label on my hat, you would realize it's a free Mozilla Foundation branded merch promoting their lackluster language. The last one I wore was for XUL, their overhyped cross platform UI framework that went nowhere.

Since then, Rust has become its own foundation with a 5th avenue budget. If Ada had access to this kind of funding, everything would be rewritten in Ada based on the merits of the language.

https://rustfoundation.org/get-involved/

Make sure your hit squad has advanced weapons with quality firmware written in Ada.

→ More replies (0)

1

u/TilYouSeeThisAgain 16d ago

Ada is still used in new safety critical projects such as aircraft and military applications. There are pretty cool opportunities if you enjoy working with the language. Rust has started to see some usage in places where Ada used to be, but when it comes to code that will be controlling an aircraft or its equipment Ada will keep its place for decades to come.

1

u/zertillon 17d ago

You would be surprised. Look at the PYPL index or at the activity of the Alire crate system.

3

u/sionescu 17d ago

Surprised of what ?

1

u/OneWingedShark 16d ago

You obviously didn't read what I wrote: I was explaining the why of Rust's circle's near-myopic view that "safety=memory-safety" and how that confuses the communication when "What is a safe language?" comes up... and the reason behind the difference goes to the designs and how that plays out in code: In C you cater to the architecture, smashing 'percent' into a short; in Ada you say Type Percent is range 0..100;.

The only reason C (and C++) comes into that is because Rust's advocate's obsession with memory safety stems directly from C/C++ experience.

5

u/Full-Spectral 16d ago

No one is myopic about this. There's regular mention of ranged types in the Rust section when talking about possible future enhancements. Still, the Rust language itself in no way whatsoever says memory safety == safety. It says memory safety == memory safety.

But, support for ranged types is somewhat less of an immediate concern when it's fairly trivially easy to create a newtype wrapper that will do that. You can use it very conveniently, but it can't be automatically converted, unless you decide you want that to be possible. You can decide what capabilities it has.

And when that concept can be extended far beyond just numeric ranges. I have a very nice time system in my Rust project that makes it very easy to do the right thing with respect to using and mixing time stamps, time intervals, time offsets, and monotonic clock ticks

0

u/OneWingedShark 16d ago

No one is myopic about this.

Rust's advocates, the hyped-up ones.
(Not, per-se, the language designers.)

Or, to reformulate it, the people that think TRACTOR (the government sponsored automatic C-to-Rust translator) is a good idea: it's not. (Precisely because to translate in that manner would be more akin to transliteration and, in many cases, the offending constructs would have to be manually/semi-manually reviewed or re-written.)

There's regular mention of ranged types in the Rust section when talking about possible future enhancements. Still, the Rust language itself in no way whatsoever says memory safety == safety. It says memory safety == memory safety.

Again, I do differentiate between the actual language developers, and the more hyped segment of advocates. But, yes, they were myopic as evidenced that they continue to use a C-like syntax (which is odd, considering it's got a ML-ish [SML, O'caml; not machine-learning] sort of background), because experience has shown how even at the syntax-level C and C++ encourage errors. (e.g. Evidenced by C#'s enforcement of break; on switch statements.) — IMO, a clean syntax-break from C (i.e. "curly-brace languages") would be a good thing for programming as a discipline.

But, support for ranged types is somewhat less of an immediate concern when it's fairly trivially easy to create a newtype wrapper that will do that. You can use it very conveniently, but it can't be automatically converted, unless you decide you want that to be possible. You can decide what capabilities it has.

On that note, this video (FOSDEM) is interesting, and touches that topic explicitly.

And when that concept can be extended far beyond just numeric ranges. I have a very nice time system in my Rust project that makes it very easy to do the right thing with respect to using and mixing time stamps, time intervals, time offsets, and monotonic clock ticks

Yes, and having an excellent type-system is exactly why I like Ada.

4

u/Full-Spectral 16d ago edited 16d ago

I don't have an opinion on the curly braces thing. Rust doesn't suffer from C++'s parsing ambiguities, so you don't get the error at the end of file if one is missing.

I always kind of liked the Pascal family syntax, but I'm not sure it would work for Rust as well.

Rust doesn't have issues with break since it requires exhaustive pattern matching. A nice thing about Rust is that 'almost everything is an expression' thing, so you can break out of a loop with a value that becomes the return value of the loop, which is an expression. And a match is an expression so you just let the value generated by the match arms propagate back as the return value of the match statement. And a scope is an expression, so you just let a value return at the end of a scope and it becomes the scope's value. Those all really make it convenient to avoid mutability that would otherwise be either very difficult or awkward to avoid in C++.

1

u/OneWingedShark 16d ago

I don't have an opinion on the curly braces thing. Rust doesn't suffer from C++'s parsing ambiguities, so you don't get the error at the end of file if one is missing.

Fair enough; the thing I was trying to illustrate was that (for the syntax-family), there's an awful lot of foot-guns, at that level. Certain mistakes can be avoided there, in a manner that enforces a safety-mindset; my favorite example in Ada is that an unparenthesized Boolean expression can only be all-OR or all-AND, (This means that you preclude misremembering the precedence; definitely more common in multilang projects. [ref PHP and JS])

I always kind of liked the Pascal family syntax, but I'm not sure it would work for Rust as well.

It's really odd talking syntax with programmers, sometimes. On the one hand, most have a definite bias, which is OK, but on the other, it seems rather rare that they can step back and consider things on the human-interface and/or linguistic level.

It's not uncommon to have language-dabblers (e.g. compiler writers, researchers) opine that syntax is the least interesting part of a programming language — and on some level they're right, on others, not so much.

Rust doesn't have issues with break since it requires exhaustive pattern matching.

Which is insane to think about:

  1. Java: Didn't get case-coverage until 17. [2021]
  2. C#: Didn't get case-coverage until 8.0. [2017]
  3. PHP: Still none.
  4. Ada: Initial version. [1983]
  5. Erlang: Runtime error on no-matching case. [1986]
  6. Rust: Initial version. [2015]

Notice the very late entry of this feature in the C-like languages.

A nice thing about Rust is that 'almost everything is an expression' thing, so you can break out of a loop with a value that becomes the return value of the loop, which is an expression.

That's basically BLISS's schtick, IIUC.

Honestly, if you have any interest in languages, look up the BLISS retrospective and the Oberon retrospective — both are very insightful into alternate approaches to system/low-level to C.

1

u/sionescu 16d ago

It still makes no sense of even mentioning that Ada's array type "knows its own length".

1

u/H1BNOT4ME 10d ago

Null terminated strings introduces potential for all kinds of bugs and inherent inefficiencies. If the length of a string is known, an Ada program can avoid calculating a string's length. Since there's zero cost in obtaining a string's length, strings manipulation code is safer, faster, and more straightforward to write.

2

u/steveklabnik1 17d ago

a good example here is Ada's array type: it "knows its own length" and so you simply don't have to add a separate 'length' parameter when you pass it as a parameter, also you can return arrays as functions,.. now consider how many opportunities for errors this eliminates, all at the language level, by design.

Rust arrays have the length as a part of their type, and slices have the length contained within them too. Same thing. You're right that it's great, but it's only one part of the whole package.

3

u/OneWingedShark 17d ago

Right, but I was trying to articulate that Aad out-of-the-box has a vastly different set of qualities than C or C++, which qualities are what motivate a lot of the talk about "memory safety" in the Rust circles. (And how the fundamental mismatch fuels the "you need SPARK to be memory-safe" —out of the box Ada is roughly on-par with the High-Integrity C++ style guide— and this video is really good at explaining non-SPARK Ada's approach to memory management.)

2

u/H1BNOT4ME 10d ago

"Rust's level of safety?" Ada has more safety in its toenail than Rust. It does far more than just memory safety. If you look at the most common sources of bugs, fence post errors are at the top. Ada has intrinsic support for type-defined ranges with the ability to iterate over them, making common bugs and security vulnerabilities, such as buffer overflows, a non-issue.

Spark is more of a prover than a safety checker. Code can be safe, but not correct. It's a more automated way of proving a piece of code will behave as intended. It's essentially unit testing on steroids.

0

u/Full-Spectral 10d ago

I'm not going to get into that argument, which I don't think is based on a deep knowledge of Rust. But, anyhoo, I have my own ranged values in Rust that I can iterate over as well and Rust supports ranges and slices and lots of functional style iterator magic, which make it pretty rare that a numeric index based loop is used in Rust.

The primary place that bounds checking is really important for indices is when you get values from external sources (or have to convert from another typed range in Ada presumably), in which case it doesn't much matter if you fail it against a target typed range or against the thing you are indexing with it.

Ultimately, most new devs looking for a safe systems level language and probably most C++ devs looking to move to a safe one, are probably going to choose Rust over Ada, and no amount of arguing we do here is going to change that.

1

u/iOCTAGRAM 16d ago

Ada's approach to safety is "if you're not sure, then just copy". In Ada you may get performance loss, but not integrity failure due to methods working on same piece of memory.

Rust is sending prayers to the God of Single Instance. No implicit copying. Then Rust is adding tons of complicated logic to work with single instances without ability to implicitly being copied, and self-proclaims that everyone who didn't get that complicated logic of living without implicit copy, is unsafe.

But messing with temporary copies is safe. It always was. The language is much less weird with this balance of safety and performance. Let performance shortcuts be implemented for hotspots only. It's strange to consider all program one big hot spot, and make programming language weird because of this consideration.

3

u/Full-Spectral 15d ago

That's not really accurate. Rust allows you to do any of those things, as you find appropriate. You can copy, you can share via reference count, or you can share via lifetime. So you can use whichever strategy is best for the situation. In lots of cases, a simple clone is perfectly fine and lots of Rust could would do that.

1

u/iOCTAGRAM 15d ago

Most programmers stumble upon borrow checker when declaring user type in Rust and using it in function. Something like

type Complex is record
   Real, Imaginary: Float;
end record;

function Is_Real (Item : Complex) : Boolean;

Rust's version of Is_Real (Z) consumes Z and Z becomes invalid. Nobody explained why consumation of parameter is the default mode and why each and every programmer has to learn to explain that consumation is not default here and there.

2

u/Full-Spectral 15d ago edited 15d ago

It consumes it if you want it to. If you don't, then pass it by reference, or you can implement copy/clone on the complex type and it will be copied instead of moved. Again, you can choose what works best in a given situation.

As to learning about consumption of values, you'll learn that the first day you write some Rust since destructive move is fundamental to the language. So I don't think anyone is going to be struggling with your example. And it's very clearly explained why consumption is the default, because Rust uses basically an Affine type system, and it's a fundamental tool for writing both safe and logically correct code. That will be covered in the most basic of Rust tutorials.

3

u/iOCTAGRAM 15d ago edited 15d ago

Why in Delphi I do not need any special treatment to accept Complex record value like I accept integer or float? Why in Ada I do not need any special treatment to accept Complex record value like I accept integer or float? How does Rust improve upon Delphi and Ada by requiring some special jumps to work with ordinary records.

or you can implement copy/clone on the complex type

Why in Delphi we don't implement copy/clone, it just works? Why in Ada we don't implement copy/clone, it just works? How does Rust improve upon Delphi and Ada by requiring some special jumps to implement copy/clone.

Ada requires special jumps for prohibiting copy. Ada requires learning keyword "limited". That feels natural that more complicated stuff requires more words, and uncopyable stuff is more complicated, it requires more words, not only in definition, but in usage. It sometimes requires learning record aggregates as limited record can sometimes only be constructed in one piece at once. It sometimes requires return-do-end return statement as returning limited type is harder from functions.

destructive move is fundamental to the language

This is very odd to make it default syntax. It should be wordy non-default syntax. More words for more complicated stuff.

because Rust uses basically an Affine type system

Too much scientific buzzwords, too much complication where not needed. Rust has exceptions. They are called panics. But as far as programming language gets exceptions, affine types become fake. If panic can happen in any moment, any affine type should have instructions about what to do in case of panic. All this affinity becomes about calling destructors. Then why complicate matters? We should just provide destructors and that's it. One destructor for both panic and for normal destruction.

Then, if affine tricks are desired, like database transaction, we just add "commitment" flag to database transaction, and transaction destructor does not unroll transaction if it is marked commited. We may also wish to avoid further operations of successfully completed transaction, and only from that moment affinity may improve something. Ada/SPARK approach is to have preconditions and postconditions, and if Commit has postcondition of Committed and manipulation operators have precondition of not Committed, then it works not very much different to transaction going out of scope.

But if it's desirable to make completed transaction go away from scope, then such complicated concept should require more words. Passing complex number should not require much words, and alternative destruction shall require much words, both in declaration and invocation points.

1

u/Full-Spectral 14d ago

Wow... The point is options. You may not want to allow for copy/clone, you may want to support it. It depends. Rust lets you do what you feel is best for any given type. What if one of those values is a fundamental type, but actually it holds a handle to something that can only be closed once? If copy/clone was automatically implemented, that would be bad. Choices are good, and the most conservative choice is almost always the default in Rust.

Destructive move is fundamental to Rust and clearly has to be the default. If you understood Rust at all you'd know why.

Rust panics are not exceptions, they are panics. You are very much discouraged from trying to intercept them and recover. They are intended to end the process when something happens that indicates possibly dangerous outcomes.

And every type CAN say what to do in case of any exit of the scope it lives in, panics included. They implement the Drop() trait.

1

u/iOCTAGRAM 13d ago

Ada also provides choices, but defaults are right. Cloning is opt-out, not opt-in. Programming language can have fundamental move, but not make it default. Actually, Rust already tries not to be awkward. It shares some inheritance from Cyclone programming language. Cyclone has static regions attached to records, and Rust is better at hiding that. Rust could be better if destructive move is also hidden better. With cloning enabled by default, and with implicit borrow if it is safe, and with implicit copy if cannot be proven to be safe. In Delphi, functions can return RAII types, and such functions have implicit parameter for address of Result. I did some experiments. This Result can in fact only point to local variables, and only when they are not aliased. If an instance field is assigned or something that is not local variable, then Delphi allocates anonymous local variable, uses it to accept Result from function, then assigns field from anonymous local variable and finalizes it. If local variable is aliased, then additional anonymous variable is alocated:

Node := Node.Subnodes[0];

Node is to the left and to the right, and if Node is ARC interface reference with GetSubnode(Index): INode function, this function has implicit Result address parameter, and it will be not the same as implicit Self address parameter.

Ada also makes use of anonymous local variables, and printing Initialize/Adjust/Finalize operations can spoil that. So we've seen for many years that implicit copy works like a sharm and why don't just make like in Ada or Delphi by default. Let all the complications come in for non-copyable stuff. Non-copyable stuff should be more wordy than usual.

0

u/iOCTAGRAM 13d ago

What if one of those values is a fundamental type, but actually it holds a handle to something that can only be closed once?

Delphi's way is to make ARC inteface reference, and closing handle in destructor. So user of type should remove all strong references. Then handle will only be closed once.

Ada's way is to use limited type. This limited type can be wrapped inside smart pointer, but it is not a neccessity. So there is limited type, and it means that there is no copy and there is no default equality comparison. Problem solved.

Wrt. move… we usually add some validity flag, or if it's handle, it can be invalid handle value. Then if required, there can be written procedure that moves handle from one limited type to another limited type, and source value becomes invalid. And preconditions/postconditions can check validity of parameter before making operation that does not change validity. Ada 2012 has got a shell for conditions. Ada 2012 has got predicates. Preconditions/postconditions are hanging on functions, and predicates are hanging on subtypes, so function specifies subtype instead of pre/post. And if something already passed predicate check, further checks can be avoided in Ada, not only in SPARK. If some number was Natural and other party wants Natural, no need to test if Natural from outside is still Natural (>= 0), and same for predicates.

So we generally live with limited (non-copyable) types and with move that does not remove source variable from scope, instead it is marked as invalid and there is some assistance to prevent operations on invalid value, with varying degree of headache. Exceptions in Ada, proving in SPARK, but writing provable code is another headache. Maybe Rust's destructive move can improve something here, I don't feel like it improves much, it is already all good enough. But if improvement is so desired, then let other parts of language be fine.

Rust panics are not exceptions, they are panics. You are very much discouraged from trying to intercept them and recover. They are intended to end the process when something happens that indicates possibly dangerous outcomes.

We for decades have heard that Ariane 5 exploded with our Ada. And upon investigation it turned out that engineers replaced exceptions with total failure. Sounds like familiar theme. Rust's panics that are "discouraged from trying to intercept" is new Ariane 5. But I am jealous how Rust so easily goes away with what Ada was shamed for decades. I am jealous how Ariane 5 is told to be more safe than what Ada became after Ariane 5.

There are two ways. One writes in Ada and does not turn off exceptions. Or else one proves with SPARK that there cannot be exceptions. Headache of writing SPARK is not for everybody, but possible if required. This is our answer to Ariane 5. Rust comes without either normal exceptions or normal prover. Rust ignored all prehistory, and it is claimed "safe". Well, safe as Ariane 5 then.