Live data from Hacker News

A Usable C++ Dialect That Is Safe Against Memory Corruption

ithare.com

91–100 of 103 posts

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#91
post #71

If anyone is really interested in this sort of thing, I suggest you take a look at SaferCPlusPlus[1]. It is "A Usable C++ Dialect That Is Safe Against Memory Corruption" (including data races). And it already exists. And I think it's better than this proposed dialect in that most of the (safety) restrictions are enforced without requiring extra tooling, and it's much less restrictive. Most existing C++ code can be co…

I happen to like quite a few things from it, but... there is a Big Fat Hairy Difference(tm) between "safe" and merely "safer". Make it "guaranteed to be safe" (which will most likely require tooling) rather than merely "safer" - and I will be the first one to promote it myself :-). Also - it would be gr8 to reduce the number of different concepts developer needs to remember about while programming. In OP (assuming th…

> "guaranteed to be safe" (which will most likely require tooling) rather than merely "safer" - and I will be the first one to promote it myself :-)

Well I hope so, as SaferCPlusPlus could use a little promotion :) At this point, despite the name, I think the dialect itself is "safe", not just "safer". Potentially unsafe features are relegated to the "mse::us" namespace and are generally only needed for interop with legacy code.

Of course there's no way to enforce that a programmer stick to the "dialect"/subset without some tooling. But I think the "rule" for adhering to the SaferCPlusPlus subset is pretty simple and generally intuitive. That is, "any potentially unsafe element of C++ should be avoided". And I think most of us have a pretty good sense of which C++ elements are potentially unsafe. The (few) unintuitive ones might include things like the implicit "this" pointer (which is a native pointer, so it should be avoided). A tool to enforce adherence would be fairly straightforward to write. When there is sufficient demand it will be provided. At the moment an "auto-translation"[1] tool has higher priority.

> quite simple: there are only 3 concepts, with 2 of them ('naked' and 'owning'=unique_ptr) being already very familiar

It's the same with SaferCPlusPlus, but the 3 "concepts" (pointer types) are "reference counting" pointer (=std::shared_ptr), "registered pointer" (= safe, unrestricted naked pointer), and "scope" pointer (basically a naked pointer that is restricted to stack allocation (i.e. a "local variable") and can only point to objects guaranteed to outlive it). The Core Guidelines also has three "concepts"/pointer types (shared, unique, and naked). I suspect that some experience will reveal that SaferCPlusPlus has the better set of pointer types.

...

Oh wait, you're the author of the article? In which case, I think it would be worth your time to get more familiar with SaferCPlusPlus. Even if just as research for your own dialect. But with some experience, I think ultimately you might be convinced by the SaferCPlusPlus solution. In fact, there's no reason why your (Re)Actors couldn't be implemented on top of SaferCPlusPlus, which would automatically provide the safe "collections" that you need.

Don't hesitate to post any questions in the "issues" section[2].

[1] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...

[2] https://github.com/duneroadrunner/SaferCPlusPlus/issues

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#92
post #90

Earlier quoted context omitted.

> use the type system to have the compiler statically check the pointers are live It doesn't explain how it would statically ensure that a moved-from unique_ptr (or equivalent) can not be used. In fact the only mentions of moves are that owning pointers can only be moved and soft pointers can be moved or copied, but C++'s move does not remove any access, it just moves the content leaving the moved-from object in a "v…

> Rust's (safe) pointers and references don't throw exceptions on explicit use Which essentially goes at the cost of having Java-style semantic memory leaks (very generally, _any_ kind of keeping-an-object-as-long-as-at-least-one-reference-exists suffers from it) => we still have to pick our poison (personally, I _strongly_ prefer to avoid refcounting, and it does work like a charm in a few very serious million-LoC/b…

> Which essentially goes at the cost of having Java-style semantic memory leaks (very generally, _any_ kind of keeping-an-object-as-long-as-at-least-one-reference-exists suffers from it)

Rust references work the opposite way. References don't extend the lifetime of their source, and a reference outliving its referent is a compile-time error.

> we still have to pick our poison (personally, I _strongly_ prefer to avoid refcounting, and it does work like a charm in a few very serious million-LoC/billions-transactions projects, but I do agree that opinions may differ).

I have no idea what the hell you're talking about, but you seem to suffer from pretty significant misunderstandings.

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#93
post #90

Earlier quoted context omitted.

> Rust's (safe) pointers and references don't throw exceptions on explicit use Which essentially goes at the cost of having Java-style semantic memory leaks (very generally, _any_ kind of keeping-an-object-as-long-as-at-least-one-reference-exists suffers from it) => we still have to pick our poison (personally, I _strongly_ prefer to avoid refcounting, and it does work like a charm in a few very serious million-LoC/b…

> Which essentially goes at the cost of having Java-style semantic memory leaks (very generally, _any_ kind of keeping-an-object-as-long-as-at-least-one-reference-exists suffers from it) Rust references work the opposite way. References don't extend the lifetime of their source, and a reference outliving its referent is a compile-time error. > we still have to pick our poison (personally, I _strongly_ prefer to avoid…

I'm still speaking about reference-counted RC, which inevitably suffers from memory leaks. And moreover - _any_ implementation which avoids throwing an exception, in quite a few use cases has no other choice than to resort to keeping the stuff until the last reference to it is killed, inevitably causing Java-style semantic memory leaks.

P.S. FWIW, Rust's references ~= OP's "naked pointers" (NOT 'soft pointers'), and SaferCPP's 'scoped pointers'. A useful tool, but is not sufficient in quite a few real-world use cases.

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#94
post #70

Earlier quoted context omitted.

> that performance is worse due to the checks. I'd argue that use cases for 'soft pointers' are about the same as that of Rust's RC , which also incurs runtime costs (very briefly - there is no magic here, neither with Rust). > The key idea and massive difference from standard C++ is that object destruction is delayed until a "quiescient state" happens If you're speaking about OP - clarification: it is not "object de…

> I'd argue that use cases for 'soft pointers' are about the same as that of Rust's RC , which also incurs runtime costs (very briefly - there is no magic here, neither with Rust). The article's "soft" pointer does not own its contents and depends on an owning pointer, so the semantics are much closer to Rust's references. In fact, the article draws an analogy between soft pointers and weak_ptr. And of course derefer…

> so the semantics are much closer to Rust's references.

Not really; I see Rust references (enforced in compile-time) ~= OP's "naked pointers" with limitations on scope. 'soft pointer' is an alternative to long-living pointers (can be replaced with a refcounted ptr a-la RC, but TBH I don't like refcounted stuff for several reasons).

> You can have either shared-memory concurrency

You can, but it doesn't work, suffering from all kinds of problems (from being _crazily_ error-prone, via being _fundamentally untestable_!, and all the way to being fundamentally unscalable, and sucking Big Time performance-wise - the last one unless we're speaking about RCU etc., but probably we aren't).

> And explicit asynchronous API (à la JS or C#) are dreadful.

Did you see C#'s await? And BTW, as practice shows, synchronous alternatives are MUCH more dreadful than even OO-style async handling (fundamental non-testability of shared-memory stuff, even if taken alone, is already enough to rule shared-memory stuff out for good - which BTW is already happening; there is a Really Good Reason for Go's philosophy of "share memory by communicating, not communicating by sharing memory").

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#95
post #73
post #71

Earlier quoted context omitted.

I happen to like quite a few things from it, but... there is a Big Fat Hairy Difference(tm) between "safe" and merely "safer". Make it "guaranteed to be safe" (which will most likely require tooling) rather than merely "safer" - and I will be the first one to promote it myself :-). Also - it would be gr8 to reduce the number of different concepts developer needs to remember about while programming. In OP (assuming th…

SaferCPlusPlus has two big issues which prevent me from using it: confusing class naming and too many concepts. I do like the ideas it builds on, and I will probably implement a simplified version for my needs...

Yes, documentation and class names are not SaferCPlusPlus' strong points at the moment. Perhaps the easy way to get started is just to use the elements in the "mse::mstd" namespace, like vector, array, string, string_view, etc. which are just safe, compatible implementations of their namesakes in the "std" namespace.

As for the pointers, there's a slightly out-of-date article[1] that tries to explain them with examples. But a simple option is to just replace all your raw pointers with "registered" pointers. It's not performance optimal, but it's safe and simple.

But yes, better introductory documentation and examples are needed. There is not yet a forum for those picking up SaferCPlusPlus, but for now you can post any questions or suggestions in the issues section[2].

[1] https://www.codeproject.com/Articles/1093894/How-To-Safely-P...

[2] https://github.com/duneroadrunner/SaferCPlusPlus/issues

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#96
post #94

Earlier quoted context omitted.

> I'd argue that use cases for 'soft pointers' are about the same as that of Rust's RC , which also incurs runtime costs (very briefly - there is no magic here, neither with Rust). The article's "soft" pointer does not own its contents and depends on an owning pointer, so the semantics are much closer to Rust's references. In fact, the article draws an analogy between soft pointers and weak_ptr. And of course derefer…

> so the semantics are much closer to Rust's references. Not really; I see Rust references (enforced in compile-time) ~= OP's "naked pointers" with limitations on scope. 'soft pointer' is an alternative to long-living pointers (can be replaced with a refcounted ptr a-la RC , but TBH I don't like refcounted stuff for several reasons). > You can have either shared-memory concurrency You can, but it doesn't work, suffer…

> You can, but it doesn't work

Sure doesn't, I mean it's only the vast majority of concurrent systems which are built on shared-memory concurrency.

> Did you see C#'s await?

Yes, it sucks. Also, it's shared-memory concurrency.

> synchronous alternatives are MUCH more dreadful than even OO-style async handling (fundamental non-testability of shared-memory stuff, even if taken alone, is already enough to rule shared-memory stuff out for good

Async does not make things more testable.

> BTW is already happening; there is a Really Good Reason for Go's philosophy of "share memory by communicating, not communicating by sharing memory").

Go only pays lip service to that, Go is shared-memory concurrency to and through, it barely allows and doesn't enforce shared-nothing concurrency.

Shared-nothing concurrency definitely is already happening. In unices (where processes are historically the standard unit of concurrency) or Erlang (where you can't share memory) or Clojure or Haskell (where you do share memory but almost everything is immutable).

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#97
post #67
post #34

This works, although the downside compared to Rust is that soft pointer validity is checked at runtime, meaning that a program that compiles can still randomly fail at runtime and that performance is worse due to the checks. The key idea and massive difference from standard C++ is that object destruction is delayed until a "quiescient state" happens in what is a reframing of RCU [ https://en.wikipedia.org/wiki/Read-c…

I don’t understand your comment about how rust pointers are safer than soft pointers. The article explains how to implement a wide variety of pointer semantics, all of which are memory safe (throw an exception on explicit use after free, use the type system to have the compiler statically check the pointers are live, use dynamic cast, etc). Looking online, I see that people implement all the same primitives in rust,…

“Throw an exception on explicit use after free” is “memory safe” in exactly the same sense “throw an exception on explicit use of an operation on an argument of the wrong type” is “type safe”. In other words, not at all.

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#98

Earlier quoted context omitted.

I suppose that's true, yes. My point was more that your compiler is not hitting a line of code and saying "I know this is or is not UB" because it can not do that in the general case - it is definitely incorrect to say it is 'proving' this. Would you consider that an accurate representation?

> My point was more that your compiler is not hitting a line of code and saying "I know this is or is not UB" because it can not do that in the general case I think it's still a (cause of) misunderstanding. For the compiler, UBs are situations which axiomatically can not occur . "This is UB" is not a concept, because the compiler assumes at all point that UBs can not occur. The compiler doesn't go "oh you're derefere…

> The compiler doesn't go "oh you're dereferencing a pointer which may be null, fuck you", it goes "you're dereferencing a pointer so it can't be null, and thus I can remove anything assuming possible nullability".

:) This is what I'm trying to say.

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#99
post #94

Earlier quoted context omitted.

> so the semantics are much closer to Rust's references. Not really; I see Rust references (enforced in compile-time) ~= OP's "naked pointers" with limitations on scope. 'soft pointer' is an alternative to long-living pointers (can be replaced with a refcounted ptr a-la RC , but TBH I don't like refcounted stuff for several reasons). > You can have either shared-memory concurrency You can, but it doesn't work, suffer…

> You can, but it doesn't work Sure doesn't, I mean it's only the vast majority of concurrent systems which are built on shared-memory concurrency. > Did you see C#'s await? Yes, it sucks. Also, it's shared-memory concurrency. > synchronous alternatives are MUCH more dreadful than even OO-style async handling (fundamental non-testability of shared-memory stuff, even if taken alone, is already enough to rule shared-me…

> I mean it's only the vast majority of concurrent systems which are built on shared-memory concurrency.

...which doesn't mean they work (~="they pretend to work, but happen to fail much more often than they should"). Just one example - one system which has 50% of one multi-billion-dollar industry and was built as shared-nothing concurrency (not Clojure or Erlang FWIW), has 5x lower downtimes that industry average; and I don't even remember how many times I've run into all kinds of shared-memory bugs in standard! libraries (my first article on such bugs was back in 1998, the last one was about a standard proposal made in 2015 IIRC); and so on and so forth.

>> Did you see C#'s await? > Also, it's shared-memory concurrency.

Nope; await (and C++ co_await BTW too) is single-threaded concurrency (nothing is shared between threads, and overall the concept is close to fibers with a bit more intuitive semantics), which makes it an extremely good building block to build a shared-nothing concurrency (the one which doesn't suffer from all the troubles of shared-memory one).

> Go only pays lip service to that, Go is shared-memory concurrency

You're somewhat right, but actually there are two different things there - one is language as such (and indeed, goroutines are shared-memory :-( ); however, Go "best practices" effectively say "don't use it", and LOTS of ppl from different projects were telling me that they do work in shared-nothing way (enforcing it is a different story though ).

> Shared-nothing concurrency definitely is already happening.

...in particular, with Node.js (which is ugly but still better than synchronized nightmare), C# await, and C++ co_await. As I told in one of my presentations (I think it was the CPPCON one on "8 different ways to do non-blocking"): you don't need to use Erlang to have reasonably-good concurrency :-). Overall, this sync-vs-async question is rather orthogonal to the programming language (heck, out of those 8 ways - which are largely equivalent to each other differing only in syntactic sugar - at least 2 will work even in asm).

> Async does not make things more testable.

It does - and very strictly too. Very briefly, async can be made reproducible, and whatever-is-reproducible, can be made testable; OTOH, making a non-trivial shared-memory program reproducible is next to impossible in any realistic environment (reproducibility ~= determinism, and thread context switches are non-deterministic at least for our purposes; heck, even VM guys weren't able to make them deterministic - which got lots of interesting implications which won't fit here). For discussion on determinism and testability - see my other presentation (ACCU 2017 one, on deterministic distributed systems or something).

Re: A Usable C++ Dialect That Is Safe Against Memory Corruption

#100
post #67

Earlier quoted context omitted.

I don’t understand your comment about how rust pointers are safer than soft pointers. The article explains how to implement a wide variety of pointer semantics, all of which are memory safe (throw an exception on explicit use after free, use the type system to have the compiler statically check the pointers are live, use dynamic cast, etc). Looking online, I see that people implement all the same primitives in rust,…

“Throw an exception on explicit use after free” is “memory safe” in exactly the same sense “throw an exception on explicit use of an operation on an argument of the wrong type” is “type safe”. In other words, not at all.

That's not a strong analogy. Memory safety almost always involves some type of runtime checks: even memory safe languages usually (always?) have runtime checking of array bounds, for example.

So it's understood that a memory safe language will generally be composed of syntactic and semantic features which help at compile time, and runtime checks to close any remaining holes. If some particular implementation happens to have a few more things in the latter category compared than is usual, that doesn't prevent it from being "memory safe", it just makes it more awkward to use and prone to bugs.

In principle, the same distinction applies even to type safety.

Post reply on HN