Circle C++ with memory safety
circle-lang.org
circle-lang.org
Val (now HyLo) says it wants to solve this problem... but it doesn't yet have the C++ interop stuff, so, it's just really a completely different language nobody uses.
Carbon wants to ship a finished working Carbon language, then bolt on safety (somehow) but, only for some things, not data races for example, so, you still don't actually have Rust's Memory Safety
Cpp2 explicitly says it isn't even interested in solving the problem, Herb says if he can produced measurements saying it's "safer" somehow that's good enough.
It's interesting how many good ideas from Rust just come along free for the ride when you try to do this, do you like Destructive Move? Good news, that's how you make this work in Rust so that's how Circle does it. Exhaustive Pattern Matching? Again, Circles does that for the same reason Rust needs it.
It is certainly true that this is not the Only Possible Way to get Safety. It would be profoundly weird if Rust had stumbled onto such a thing, but "Let's just copy the thing that worked in Rust" is somehow at once the simplest possible plan that could work and an astonishing achievement for one man.
I'm not sure this is correct. As I understand it, Carbon's plan is to add a borrow checker like Rust's.
From a recent talk[0][1] by one of the lead developers:
>Best candidate for C++ is likely similar to Rust’s borrow checker
[0] slides: https://chandlerc.blog/slides/2023-cppnow-carbon-strategy/in...
[1] relevant timestamps:
The borrow checker isn't enough on its own to solve this in Rust, Sean explains (probably deeper in Circle's documentation) that you need to track a new property of types which is infectious, Rust does this with the Send and Sync traits.
Er, the slide title says that solving this is highly desirable, just not a strict requirement for security purposes.
Not sure how that's the same as "doesn't see a reason to solve data races". I see lots of reasons. I just think it is possible to achieve the security goals without it.
FWIW, I'm hopeful we'll end up including this in whatever model we end up with for Safe Carbon. It's just a degree of freedom we also shouldn't ignore when designing it.
>A key subset of safety categories Carbon should address are:
>[...]
>Data race safety protects against racing memory access: when a thread accesses (read or write) a memory location concurrently with a different writing thread and without synchronizing
But then later in the doc it says
>It's possible to modify the Rust model several ways in order to reduce the burden on C++ developers:
>Don't offer safety guarantees for data races, eliminating RefCell.
>[...]
>Overall, Carbon is making a compromise around safety in order to give a path for C++ to evolve. [...]
One could read this as saying that guaranteed safety against data races is not a goal. Perhaps this doc could be reworded? Maybe something like "Carbon does not see guaranteed safety against data races as strictly necessary to achieve its security goals but we still currently aim for a model that will prevent them."
[0] https://github.com/carbon-language/carbon-lang/blob/trunk/do...
If Carbon doesn't prevent data races, then how exactly will it achieve memory safety? Will it implement something like OCaml's "Bounding Data Races in Space and Time"? [0]
If we ignore compiler optimizations, the problem with data races is that it may make you observe tearing (incomplete writes) and thus it's almost impossible to maintain safety invariants with them. But the job of a safe low level language is to give tools for the programmer to guarantee correctness of the unsafe parts. In the presence of data races, this is infeasible. So even if you find a way to ensure that data races aren't technically UB, data races happening in a low level language surely lead to UB elsewhere.
Ultimately this may end up showing as CVEs related to memory safety so I don't think you can achieve your security goals without preventing data races.
> Tearing
> For the primitive types longer than 32 bits (long and double), it is not guaranteed that reads and writes from different threads (without suitable coordination) are atomic with respect to each other. The result is that, if accessed under data race, a long or double field or array component can be seen to “tear”, where a read might see the low 32 bits of one write, and the high 32 bits of another. (Declaring the containing field volatile is sufficient to restore atomicity, as is properly coordinating with locks or other concurrency control.)
> This was a pragmatic tradeoff given the hardware of the time; the cost of atomicity on 1995 hardware would have been prohibitive, and problems only arise when the program already has data races — and most numeric code deals with thread-local data. Just like with the tradeoff of nulls vs. zeros, the design of primitives permits tearing as part of a tradeoff between performance and correctness, where primitives chose “as fast as possible” and objects chose more safety.
> Today’s JVMs give us atomic loads and stores of 64-bit primitives, because the hardware makes them cheap enough. But primitive classes bring us back to 1995; atomic loads and stores of larger-than-64-bit values are still expensive, leaving us with a choice of “make operations on primitives slower” or permitting tearing when accessed under race. For the new primitive types, we choose to mirror the behavior of the existing primitives.
> Just as with null vs. zero, this choice has to be made by the author of a class. For classes like Complex, all of whose bit patterns are valid, this is very much like the choice around long in 1995. For other classes that might have nontrivial representational invariants, the author may be better off declaring a value class, which offers tear-free access because loads and stores of references are atomic.
The key here is the last phrase: "For other classes that might have nontrivial representational invariants, the author may be better off declaring a value class, which offers tear-free access because loads and stores of references are atomic.". This implies that to avoid tearing you would need to introduce a runtime cost to every access, which is unacceptable for a language aiming to replace C++.
And you can assume that a low level language like Carbon has a lot of types with nontrivial invariants. Just like in Java, data races WILL make one thread observe a partially written value in another thread.
In the presence of data races, you can only avoid tearing when writing to fields whose size is smaller or equal than word length (typically, 64 bits). If all you have are small primitives or pointers, then it might work. But Carbon can't abide by this restriction either.
Unfortunately WG21 seems to have some issues with any ideas coming out from Circle, going back to its early days, and I don't see them being willing to adopt Sean's work.
Which is really a pity, as he single handled managed to deliver more than whole C++ compiler folks, stuck in endless discussions about the philosophical meaning of the word safety.
Maybe at least some vendor in high integrity computing domain does adopt his work.
What reasons? Are those valid?
"Don't constexpr All the Things", from CppNow 2021,
When I first heard about it, a lot of the ideas seemed interesting, but are there users of it?
I think my biggest question is "what is the goal here?"
For carbon they're pretty explicit that it's a research prototype. If anything is to come if it, it will need to be usable at Google's scale (they have real issues with build times, they build from head so abi compatibility isn't required, etc)
Herb wasn't really designing cpp2 as a successor language so much as a playground to understand what features might make sense to propose for adoption in c++.
What is circle? It's more than just some project, but the ideas haven't been adopted in cpp and the compiler repository isn't being updated
From a practical standpoint, when you run your tests with msan and asan (and if you have decent test coverage) I'm not convinced of the benefits of memory safety that eg rust provides
Supposing that it is worth it though, why not migrate to rust? I have a friend starting a startup / consultancy to do just that, and it makes more sense than using circle. Even carbon says "if you can use rust, use rust not carbon"
A key selling point for Circle is the “superset of C++” aspect. What you’re proposing—while feasible for startups—is entirely unreasonable for larger companies, especially those with existing [usually massive] C++ codebases. With enough evangelizing, you might get them to agree to start using something like Rust in new, smaller, and internal projects. However, suggesting a rewrite of anything mission-critical that can directly impact the bottom line to an entirely different language, ecosystem, behaviors, guarantees, community, etc. is one of the most scary proposals you can make to a company. Are the existing engineers proficient in this new language? How long will it take to ramp up? Are there any new costs (not just financial) to adopting this new language (hint: there always are)? Are there legal concerns? How long will the rewrite take (hint: likely longer than the engineers think)? The list goes on. It’s simply too risky a proposition.
I say this confidently because I've worked directly with people doing this and seen their work.
I will claim one better and say: you can migrate to idiomatic rust (with help from some custom libraries)
Should companies do this? Depends on the industry and the needs for the kind of safety rust provides
The advantage of compile time verification is that you can prove that certain paths and behaviours are impossible and don't need to be tested. This reduces the space of required tests combinatorially. We all already do that: no one bothers testing (for example) that standard library functions work correctly in their own code base. In Rust (and strongly typed languages in general) there are entire classes of test and assertions that aren't needed anymore.
MSVC is for and from the same people as Windows, Windows is large enough and popular enough that it isn't going away (Microsoft would have to die - and even if that happens you can bet organizations the US government would take over Windows), thus betting that MSVC won't go away is safe enough. If it doesn't work out your company is in trouble but so is everyone else. It is when you are betting on something less popular that you get into potential trouble as the thing you depend on can be canceled.
Mingw isn't as good as MSVC, but if forced you could use it instead. Which means you have options and so a bet on closed source has a understandable worst case risk that is a lot better.
Small close source projects like Circle C++ should be used either as a small experiment - if this fails you can rewrite everything in something open source in a few months and so the risk is low. There is one other common option (though I don't know what circle c++ offers): you can bet on circle C++ after your lawyers get a contract that if it becomes unavailable you get source code and rights so your worst case is you maintain it yourself. These contracts in business are made all the time, and so a good lawyer will have no problem getting the risk terms into a contract.
That said, I'm wholly uninterested in any proprietary language, too.
Modern C++ provides all the tools to build an alternative standard library from the ground up with most behaviors and interactions being defined and safe. This always seemed like lower-hanging fruit for a first attempt than changing the language.
C++ is commonly used in contexts where object lifetimes and ownership are inherently unknowable at compile-time, safety can only be determined at runtime. This is why memory safety annotations like 'std::launder' exist in C++. Trying force these cases into compile-time memory safety box is a source of much friction when using languages like Rust. They handle the 80% case where compile-time safety, destructive moves, etc make things easy for the developer but then significantly worsen the complexity, and therefore safety, of the other 20%. This isn't necessarily a bad thing but it intrinsically creates a market for C++ which explicitly allows you to handle these cases in a reasonable way.
Systems programming is full of edge cases that break tidy programming language models. This has to be accommodated reasonably in languages that want to wear the mantle of "systems language". Zig is an example of a new systems language that does this well in my opinion.
And yes, many of these issues were already solved by other system programming languages outside the UNIX linage of programming languages.
It also goes to a great lenght to embed standard library into base language as deeply as it can, and forces unsafe constructs. Just see how many magic types from std namespace are emmited by compiler when you use range for, initializers, or, worst of all, coroutines. Lambdas with capture by reference replace old fashioned dangling pointers with much more modern dangling references (you are free to copy lambdas that reference local variables :smiling virus with thumbs-up emoji:).
There is no way to get "another library", without making it not-c++. Those guys cannot even make no-exceptions/no-rtti a part of the standard.
Because while you'll never be able to create a new standard lib for C++ it wouldn't be hard for C.
Remember discussions that led to std::bit_cast? How absolutely everyone who processes binary data is wrong, because object of type, say, "unsigned" does not exist before it is created, so everyone casting pointers to parse packets is wrong, and only commitee is right?
It is doubly sad to see them claim compatibility to C (C++ is absolutely not compatible to C) as a reason for some features, or single pass compilation as a reason for others...
[edit] To clarify, on one hand, a lot of terrible misfeatures are kept in the name of compatibility, e.g. "int x = x;" ("fixing that will break too many existing exploits") or not being able to add friends without editing source ("debugging? we think it is overrated"), but then they go and proclaim basically all existing code that processes structured binary data as non-conformant.
What does this code do? Is this referring to a previously defined x, now shadowed by a new x? Like this
int x = 1;
int x = x;
If yes, Rust has this feature too. And in Rust, shadowing variables is very idiomatic and is sometimes used to create safe abstractions. int f(int x) {
int x = x; // second x is not argument
return x;
}
and: int f(int x){
// here second x is argument
return [x=x] { return x; }();
}Are you sure that, in the first example, in int x = x, the local variable x doesn't get initialized with the function parameter x, but rather gets initialized with itself (uninitialized memory)? Is this instant UB?
Is the second example actually legit, and initializes the closure bound variable with the x function parameter?
struct S {
int x;
int f1() { int x = x; return x; }
int f2(int x) { return [x=x]{return x;}(); }
int f3(int x) { if (int x=x) return x; return x;}
// int f4(int x) { int x=x; return x; }
};
int main(){}
clang warns a bit with -Wall a.cpp:5:26: warning: variable 'x' is uninitialized when used within its own initialization [-Wuninitialized]
5 | int f3(int x) { if (int x=x) return x; return x;}
| ~ ^It's like types in statically vs dynamically typed languages. Types are unknowable at compile time when the compiler doesn't force them to be static.
And similarly ownership and lifetimes are "dynamically typed" in C++, because the compiler doesn't force them to be rigidly declared like in Rust.
Just like statically typed languages have some dynamic dispatch features, but despite that still most of their types can be known with certainty.
What specific contexts are these?
A single memory address can contain several unrelated live objects depending on when you look; a single live object can appear at different memory addresses over its lifetime without being moved; and live objects may not have a memory address at all. The hardware implicitly owns read and write references to these objects, which are not visible to the compiler and free to ignore the ownership model.
Consequently, you need a way to tell the compiler that the object it thinks exists at an address no longer does, and that object has neither been moved nor destroyed, and that there may be an unrelated object at that address that was never constructed. In these cases, compiler optimizations can generate incorrect code based on a naive analysis based on what is visible at compile-time. C++ provides ways to give this a defined behavior and indicate to the compiler when its lifetime model is incomplete so that it doesn't produce incorrect code. You can build ownership models on top of this that incorporate ownership that is not visible at compile-time.
Some C++ devs don't know these features exist, other C++ devs use them routinely. But the point is that C++ has specific features to address the case when lifetime analysis cannot be correctly done by the compiler.
Larger than the 48 bits address space of most 64 bits architectures?
If anything, Rust is even more expressive than C++ in this regard: it has no concept at all of "an object at an address". You can (unsafely) cast a pointer from any type to any other type and read it, as long as the underlying memory is properly initialized. And if you're in a context where you know it will always be valid, or if you can check the validity at runtime, you can put a safe wrapper over the conversion.
For instance, the safe function str::from_utf8() converts a byte-array pointer into a string pointer, after checking that the encoding is correct. Nothing in memory is changed, and the old and new pointers form two different views that can be used simultaneously. And it's not magic: you could copy its implementation yourself, and your function would be just as valid as the standard-library version. Contrast with C++, where all memory must have one single type at any time, regardless of how much laundering you do.
The only kinds of semantic "ownership" in Rust are access restrictions caused by the aliasing rules (which just depend on the pointers in use, not on objects in memory), and local variables and temporaries being dropped at the end of their scope (if they haven't been moved elsewhere). All the high-level lifetime rules of safe Rust are built on top of those semantic rules, and a smidgeon of unsafe Rust can subvert them as necsssary.
Thankfully we have actual data on this, not just random numbers, and no, this is not the case in practice.
Mind to share that data? I think it would be hard to get quantitative data either way.
You can find several papers about Rust in particular by searching for [rust memory safety analysis].
This seems to be adding Rust borrow semantics and borrow checking to C++. The delivery vehicle seems to be a C++ compiler that the author has been working on for a number of of years. I couldn't find a ton more on the background of this.
From a technical perspective this looks promising as a C++ successor language. The project will have to attract other members in the C++ community though.
> Memory-safe languages are predicated on a basic observation of human nature: people would rather try something, and only then ask for help if it doesn't work. For programming, this means developers try to use a library, and only then read the docs if they can't get it to work. This has proven very dangerous, since appearing to work is not the same as working.
100% 100% 100%
Memory safe languages are just predicated on the memory-safety problem being difficult to avoid for humans, because nobody has a 0% error rate. They would still be incredibly necessary and relevant even if nobody relied on "appears to work" as the measure of correctness.
The rules for safe interfaces are the same for all Rust programs, so users know what to expect. Whereas in C++ the library author has more to say what usage is supported and what isn't (e.g. Rust requires all globals to be thread-safe unconditionally, but a C++ library may say it's thread safe if you set a config option or it's your problem to synchronize access).
For example Rust provides types like core::cmp::Ordering, core::time::Duration and core::ops::ControlFlow so sometimes your API will be harder to misuse than in might have been because you know, your timeout parameter was a Duration, not an integer count of seconds (or was it milliseconds?)
But, although eventually Clippy will express doubts, Rust won't force you to rewrite that function which took N booleans and now after modification takes N+1 booleans, even though all your callers are probably a mess of true, false, true, true, false undecipherable flag nonsense and a re-factor was called for.
It's surprisingly hard to give new programmers the right instincts on this stuff. I'm pretty sure I was terrible (thirty years ago) too, so this isn't a humble brag it's just an observation.
For example, Circle implies broad overhaul to tooling and libraries while Fil-C implies no such thing. Also, Circle is all about not using existing compilers on the grounds that they are hard to experiment with, while Fil-C is a surgical change to clang/LLVM.
The main advantage of Circle over Fil-C is that if you pay the cost of that overhaul, you'll get performance that is better than what Fil-C could ever do even with a lot of optimization. Not obvious to me if that makes it net better, though.
You can sell these people something like Rust because you can very often either show why the "better performance" C++ they have is wrong (and if they wanted a wrong answer here's zero already, pay me) or sometimes actually worse performance. Not every time, but often enough to make a real difference. The Circle safety feature should be in the same ballpark.
You can't sell them anything that's just anyway going to have worse performance, if you could they'd be writing Java already. So that's counting against Fil-C.
Fil-C is all about being able to run existing C/C++ code that nobody is going to rewrite, not even in a dialect like Circle, since the burden of annotations will be too great.
Not to mention that Boehm isn’t sound on modern C compilers. Conservative stack scanning can be foiled by optimizations that are valid from the compiler’s perspective.
Compilers should definitely have a mode that stops them breaking conservative stack scanning. GC is a drop-in fix for so many memory safety problems it's a vital weapon in the toolbox. Combined with checked array and vector dereferences, you can get a long way without needing Rust or Circle style rewrites.
I don’t think it’s reasonable to expect compilers not to break conservative stack scanning. Fil-C uses accurate stack scanning instead, and it’s properly wired into the compiler and the rest of Fil-C’s type system.
It’s not necessary to allocate all locals in the heap and have 50x overhead. It’s that way now because I’m only just getting started.
Bounds checks + an unsound conservative GC isn’t worth the overhead it imposes since it still leaves your code memory unsafe.
Honest question: what about Ada? It was specifically designed to be a safe language for critical systems, and for a while was mandated for some military systems. Did the author not consider it, or are its protections just not as expansive as Rust's?
Back when Ada was new, people just didn’t actually like programming in it much. Some did, of course, but many did not. This is the reason the Ada Mandate was abandoned.
This led into a situation with a small, walled off community that didn’t really communicate with the outside world much. This has a compounding effect over time.
Ada, while designed for safety critical systems, was not actually memory safe until fairly recently. Deallocating memory at runtime wasn’t, and in my understanding, may only be in the presence of SPARK? Hopefully someone can chime in here. Now, that fine for the systems Ada tended to be used for, which often have either no dynamic allocation or a singular allocation at program startup, but for inspiration for other language designs, given that it forgoes a hard problem, it’s not really as useful to those who are trying to solve those problems. This doesn’t mean Ada is useless for inspiration, but for “how do I implement memory safety,” it doesn’t have many unique uses things to offer.
None of this means Ada is a bad language, but these are the main contributing factors that I see with regards to your specific question.
Additionally, it is common to use arenas, and many types can be dynamically stack allocated with a retry operation in case there is not enough space, so that the call can be redone with a smaller size.
Memory Management with Ada 2012 from FOSDEM 2016,
https://archive.fosdem.org/2016/schedule/event/ada_memory/
Doesn't go much into SPARK related improvements though, given its date.
Also we aren't talking about Ada83 any longer.
I have seen applications react by pausing and trying the allocation again a few seconds later --- this can be shockingly effective! https://hacks.mozilla.org/2022/11/improving-firefox-stabilit...
It is true that Ada has a safe suitable-for-systems-programming subset that's much better than most languages. I think when people say "Rust is the first safe systems programming language" they implicitly mean "that is expressive enough for me to replace C++ with".
I assume that the totality of SPARK's guarantees would get you to the same place as Rust but I don't know.
A big thing which counts against Ada (and SPARK) in practice is that it's not popular. You'll trip over Rust programmers everywhere, two of my friends are getting paid to write Rust, for completely unrelated companies, unrelated reasons, Rust seems like a reasonable fit so those companies picked Rust. You don't see that with Ada and SPARK.
Most people solved the safety problem a long time ago by switching to garbage collected languages. As times go on and garbage collector designs get better while hardware gets beefier you can get away with using a garbage collected language in more and more cases. Case in point golang marketing itself as a system programming language and succeeding, not to even mention even the game dev accepting C# these days.
To be clear, there is a niche and need for safe and non-garbadge collected languages and there will always be. I am just saying the niche is much smaller than people might think.
I think it is mistake for Rust evangelists to market safety of the killer-feature of the language. Rust is not popular because of the safety but because it lowered the barriers of entry for a new generation of system programmers by providing excellent tooling, error messages and a type system that take at least the lower hanging fruits of the academic functional world. That is all cool stuff beyond the safety circle jerk.
It only shows that Ada is very old and verbose.
I would have the reverse guess, but like you I can't think of a reliable way to get reliable numbers. Here's why I get my guess though: There just aren't that many defence IT people proportionally and only some of them are writing Ada. I saw no sign of non-defence Ada in noticeable quantities.
Sometimes people are just upfront, we're a defence contractor, all our clients are military, everybody who works here must get clearance. Sometimes they're a bit more shy, please come talk to our specialist recruiter, and fill out all these forms that are surprisingly interested in you as a person not an employee (because they're pre-screening you, you will get security cleared). Nobody was like "Ada has been so great for our CRUD web sites" or "Come join our team writing a trading platform in Ada" or anything like that.
So I think it just can't work statistically.
Wouldn't a better approach be to instead focus on adding better C++ interop into Rust? Isn't that a more forward thinking approach?
On that line of thinking, what about Swift and its C++ interop? Or is Swift not ready for systems level programming?
A lot of swift behavior is dictated by the standard library. There have been Micro-Swift and embedded swift implementations to cut down overhead along with work on move only types.
I think swift could be a good candidate for a post-c++ language but despite the open community it is highly Apple dominated in its scope and direction. That’s probably my the biggest trouble with swift.
I think the premise that a lot of experienced programmers can write good C++ is at least somewhat valid. They know to not use raw pointers, which APIs to use for bound checking, whatever. The issue is that new users don’t, and the defaults are bad.
If I have to write #feature on safety in every file, it becomes possible to forget. Opting in vs opting out
There are much more important bugs than memory safety bugs, including all sorts of performance bugs.
We need to address the whole spectrum instead of compromising all areas in order to tackle niche bugs that only matter in security-critical contexts.
Far worse is your program starts corrupting your data and suddenly you are executing a routine you had no intention of ever executing
For what it's worth, Rust (and other languages of ML heritage) are really good at getting rid of huge categories of bugs, not just memory. Not all bugs, of course.
When you don't have to worry about memory safety, your brain is freed up to worry about other kinds of bugs.
The other bit of good news is that the sorts of things Rust does to have memory safety also enable the systematic elimination of many other kinds of bugs. For example, iterator invalidation.
Would you rather get a program that constantly use all of your RAM until you restart it, or one that consistently uses very little, but someone crashes in edge cases?
Rust is a lot better than GC languages at memory usage, and (because borrowing is actually safe) a bit better than C++.
I have made major contributions to several Rust projects that have collectively saved organizations over $100MM in production, if not more. I worry about lots of things, but crashes just aren't one of them.
On Linux, the stack grows automatically and never shrinks.
The problem is accessing objects that are outside of their lifetime, not storage. The objects are gone, even if the storage might still be there.
But this isn't the hard part.
Since you need to edit your compile to modify allocations, this needs to be a global analysis. If you've got a function that takes a parameter by reference and returns it by reference, you need to adjust the allocations at the call site to be on the heap if the return value outlives the allocation. But... now you've broken separate compilation and need this to run as a global pass with lto. And enjoy all of your false positives caused by sound call graph analysis in a world where you've got virtual and indirect function calls. And what about a local that is stored by reference as a field of an object and then that object is passed by reference to a function? Now you need an object-sensitive pointer analysis. During your compile.
And then there are all of the ordinary problems with C++ that make sound static analysis nightmarish. One of these things havocs and blows everything up to Top and you are quickly yelling at your compiler.
A lot of code is written in safe, slow languages that could be written in a safe, fast language.
I am a bit worried about this. should I be?
A language which has semantic requirements and isn't willing to reject programs for which it has been unable to determine whether they meet the requirements, will have these cases by Rice's Theorem. This is why C++ is a complete disaster. C++ specifically does not allow the compiler to reject such programs.
You can defuse this like (safe) Rust by just writing conservative checking. You will reject some programs you would like to have successfully compiled, but that's actually OK, programmers who hate it are inspired to improve the checking, that's what Rust's "Non-Lexical Lifetimes" - mentioned by Sean, are about.
Even more radically, you can be a Special Purpose language and just only allow a relatively small subset of possible programs, all of which you know are correct, that's why WUFFS gets to emit indexing with no runtime bounds miss checks - it did all the bounds checks at compile time, all source where that wouldn't be possible isn't legal WUFFS code.
It's just not required to do so.
Asan, Msan, Tsan and UBSan all detects different ones.
So while "What should we do when we're sure it's not a valid program?" is easy and so it's very silly that some C++ compilers will compile that anyway, it's not enough, you also need to decide "What should we do when we're not sure?" and there the ISO document provides a firm answer, which all the compiler vendors follow - press on anyway, too bad. "Ill-formed - No Diagnostic Required" is how the ISO document describes this situation. The diagnostic isn't required because it's technically impossible to guarantee.
You might insist that "Not sure" can't happen. Unfortunately the reason it's called Rice's Theorem is that a guy named Henry Rice got himself a Maths PhD proving this in 1951.
You can well argue that Rust may be better than C++ in some dimension, just as others can argue that those qualities have an unreasonable or intractable cost (note that about 20% of all crates resort to `unsafe`). But your blanket assertion is not even supported by the reference you made.
Also the user is free to tell a C++ compiler to reject programs that use many sorts of undefined behavior, though indeed nobody would claim that any current compiler can identify all such cases. But Rice's theorem says that no Rust compiler could either.
In general I would be much happier if I could assert to the compiler that my data fits certain parameters and it would optimise based on these explicit assertions rather than on implicit assumptions (undefined behaviour). Those assertions could be turned off in the final build if we determine they actually impact performance, but I expect I'd be able to keep them on all the time.
Have you read Rice’s theorem? Every nontrivial program on a Turing machine (which has only defined behavior) is undecidable.
The problem was even when written together the impedance mismatch existed.
> circle : unsafe printf
Comeon people, if you allow unsafety, you cannot call your language safe. There are safe system languages, but don't lie and call unsafe languages safe. Partial safety is not full safety.
> rust: unsafe { println!("{}", *r1); }
Comeon people, if you allow unsafety, you cannot call your language safe. There are safe system languages, but don't lie and call unsafe languages safe. Partial safety is not full safety.
Unsafe in Rust means "soundness cannot be statically verified, the language will insert runtime checks for you and perform a clearly defined action (panic) if they are violated".
Much ink has been spilled about "unsafe" in Rust being unfortunately named.
> It is the programmer's responsibility when writing unsafe code to ensure that any safe code interacting with the unsafe code cannot trigger these behaviors.
https://doc.rust-lang.org/reference/behavior-considered-unde...
And they transverse
> However, violations of these constraints generally will just transitively lead to one of the above problems.
Right.
But as sibling points out, the rest of your sentence is incorrect. The language mostly does not insert additional, runtime checks and you are allowed to create UB-level bad behavior in unsafe blocks.
So, this only does anything interesting if r1 was a raw pointer so that dereferencing it would be prohibited without the unsafe block. If it's just a reference or some smart pointer type then that's fine anyway.
Of course you can fuck with it enough to make it unsafe, but at that point you know exactly what you're doing
In some cases you can even "fuck with it" less than std::vector and cause memory unsafety because std::vector was implemented by people who've been fucked with before, and this "safe" collection maybe was not. Pushing items from the collection itself into the collection again when it's full is often one way to cause this - the std::vector promises this works correctly.
Vector<int> v {1, 2, 3};
int *p = &v[0];
v.push_back(4);
printf("%d\n", *p); // this is UBBut yes, in my implementation I have a safe version of int called 'i32' which overrides the & operator and doesn't allow it to return raw pointers.
There are significant costs to this safety, of course, just like adding bounds checks.
From the post:
Microsoft's bug telemetry reveals that 70% of its vulnerabilities would be stopped by memory-safe programming languages.
Google's research has found 68% of 0day exploits are related to memory corruption.
These sorts of false statements make people everywhere think that memory safety is a bigger issue than it is and that it impacts almost all application development which is completely wrong. memory safety is a bigger issue than it is and that it impacts almost all application development which is completely wrong.
It specifically states that in the context of vulnerabilities so I'm not sure why it's believed to be insufficiently qualifying.With that said, while you're right to question the precisions of statements, Google says the same for their own data:
https://www.chromium.org/Home/chromium-security/memory-safet...
Around 70% of our high severity security bugs are memory unsafety problems (that is, mistakes with C/C++ pointers). Half of those are use-after-free bugs.
...so it's not just Microsoft saying that.
You'll also see this mentioned on Wikipedia:
https://en.wikipedia.org/wiki/Memory_safety
Going farther, the numbers vary, sometimes even more:
https://www.memorysafety.org/docs/memory-safety/#how-common-...
Extremely. A recent study found that 60-70% of vulnerabilities in iOS and macOS are memory safety vulnerabilities. Microsoft estimates that 70% of all vulnerabilities in their products over the last decade have been memory safety issues. Google estimated that 90% of Android vulnerabilities are memory safety issues. An analysis of 0-days that were discovered being exploited in the wild found that more than 80% of the exploited vulnerabilities were memory safety issues.
In short, I don't think the context here will change most folks' interpretation of the results--nor do I think it should. Any network-connected device is inherently multi-user and the security of that device is only as effective as the least secure program executing on that device at a particular permission level.
I can think of very few applications today that don't interact with a network in some way. In addition to that, my opinion is that most memory-safety issues that create vulnerabilities are also reliability issues. Secure applications are generally inherently more reliable applications.
NOT as a general statement about all security vulnerabilities.
So its false and wrong.
if(x % 2)
vec^.push_back(x);
unsafe printf("%d\n", x);
Skipping the braces for single line conditionals in C++ is a lazy practice that almost inevitably leads to bugs, like Apple's famous goto fail bug: https://www.synopsys.com/blogs/software-security/understandi... . While memory bugs are difficult to prevent, this particular class of logic bug can be eliminated entirely just by always remembering to write two {} around the statement, so a language concerned with correctness should promote such good practices. if(x % 2){
vec^.push_back(x);
} if(x % 2)
vec^.push_back(x);
do_something_else();
under the mistaken assumption that the second line will also only run in the x % 2 case. People make mistakes, this particular one can happen, does happen, and has happened many times, and the compiler absolutely will not inform or warn you, because it doesn't know you intended the second line to run in the same if statement as the first (because C++ isn't whitespace sensitive). Is ruling out an entire class of bug really not worth the effort of just typing two more characters?False.
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#inde...
https://clang.llvm.org/docs/DiagnosticsReference.html#wmisle...
I have seen
for(size_t i=0; i<n; i++);
foo(i);
if(a>b);
a=b;
The famous goto bug was because the assholes had no tests for that module.