7 ms·
The empire of C++ strikes back with Safe C++ blueprint
- bioneuralnet 2y ago"Rust lacks function overloading, templates, inheritance and exceptions," ...sounds good to me!
- stackghost 2y agoExceptions are great. There's an argument to be made that one should handle errors where they occur, but that's often not desirable or even possible. If I call into a library and something goes awry I don't want to have to care about the inner workings of that library. Most of the time it's sufficient for me to try/catch that call and if it fails I'll handle it as gracefully as possible at my level.
- kibwen 2y agoThe workflow you're describing is also how it works in languages without resumable exceptions, except that you're forced to acknowledge when a function call is capable of producing an error. Whether you want to ignore the error, handle the error, or propagate the error, it's all the same; you're just required to be explicit about which approach you're taking (as opposed to exceptions, where the the implicit default is to propagate).
- vacuity 2y agoIndeed. While it is painful for the people who know they have a simpler architecture, making errors and other cross-cutting effects explicit is necessary at some point. It's essential complexity that shouldn't be hidden; it should be addressed from the get-go. Although the industry largely has the wrong incentives and discourages robust, comprehensible programs.
- elcritch 2y agoMy belief is that errors should be handled using effect systems. So in the signature but not muddying the actual return types. Useful effect systems allow the end user to decide where and when to have the compiler enforce errors are handled. Nim has had an effect system for a while but became much more useful when `forbids: [IOError]` was added. It makes it easy to ensure certain type of errors are handled at specific points. More languages should embrace effect systems. Ocaml's is even used to implement multithreading support, albeit effect systems vary widely in design and theory.
- consteval 2y agoI mean this is how a lot of exceptions are handled, even in C++. You can use noexcept and whatnot and you don't have to change types and propagate them out. In Rust, you do. Java has maybe the strongest system because not only do you have to declare what can throw but it checks it at compile-time. That's, to me, a full featured effect system. But errors-as-values are all the rage today. But modifying types, especially every type in the chain, is annoying and overly manual IMO.
- elcritch 2y agoYeah, Java had some good ideas but just not quite there on the UX, like many things with Java sigh. Checked exceptions were annoying because you had to manually annotate the exceptions all the way up your chain. The list of effects should be generated by the compiler, and the IDE should show them when desired. Maybe manually annotated at external API boundaries which double as forcing the API dev to handle unlisted exceptions.
- stackghost 2y ago>you're just required to be explicit about which approach you're taking Yes and I'm opposed to such "if err != nil" boilerplate.
- worik 2y ago> Yes and I'm opposed to such "if err != nil" boilerplate. That is not what Rust boilerplate looks like.
- stackghost 2y agoI'm aware. One of the criticisms often leveled against Go is that it's needlessly verbose when handling errors, which is why I chose that example.
- 6equj5 2y agoAnd you're not opposed to try-catch boilerplate?
- stackghost 2y agoBoilerplate is code you have to write almost as a pro forma thing. If (in go lang, to continue my example) you're just going to keep copy pasting the same if statement to return `err` up to some higher caller, then why write all those lines when at the top level a single try/catch can remove potentially dozens of lines of code?
- soulbadguy 2y ago> except that you're forced to acknowledge when a function call is capable of producing an error Acknowledging the error is handling the error even if partially. The point of exceptions is to only acknowledge error that one can/knows how to properly handle
- kibwen 2y ago> The point of exceptions is to only acknowledge error that one can/knows how to properly handle In Rust this takes a single character. There's effectively no cost to having the programmer acknowledge the error, and there's a large benefit in that you now know that there's no such thing as an error that the programmer ought to have handled but was simply unaware of. That's a huge benefit for writing resilient software.
- elcritch 2y agoIt's one character in the best case. In the worse case you need to convert the error types to your error type which just re-wraps or replicates the upstream error type. Then repeat this for every library type you use. Zigs error design seems saner in this aspect at least. Rust, IMHO, just makes errors require lots of unnecessary manual labor instead of being smart about it. Alternatively everything just gets put into a `dyn trait` and you're effectively just bubbling up errors just like with exceptions, but with way more programmer overhead. The performance overhead of constantly doing if/else branches for errors adds up as well in some situations. Of course a fair bit of Rust code just uses `unwrap` to deal with inconvenient errors.
- binary132 2y agoOne thing I’ve wondered about is, isn’t the cost of checking for the failure case in the good case all the time actually worse (even if only slightly) than the cost of not throwing, which is nothing?
- vacuity 2y agoThere's indeed a predictably present cost for checking for failure all the time. Exceptions, depending on the implementation, often do come with runtime overhead too. If the determining factor is a slight performance gain of exceptions over ubiquitous checking, that would be an exceptional (ha) case. I daresay there are almost always other more salient factors, if harder to rearchitect around.
- binary132 2y agoAIUI most implementations of exceptions only carry a cost on unwind, but I'm not the expert here.
- otabdeveloper4 2y ago> you're forced to acknowledge when a function call is capable of producing an error All functions are capable of producing an unbounded set of errors. (Yes, programming is hard.)
- 112233 2y agoExceptions in C++ are the closest we have to the implementation of the COME FROM proposed in "A Linguistic Contribution to GOTO-less programming". It takes statically typed C++ and turns it into dynamically typed language. Throwspec is dead, anything can throw, except when noexcept, then nothing can throw. It is next to impossible to reason about control flow. Dynamic linking opens whole new dimension of this wormcan. Do you handle exceptions in your constructors? How about constructors of your function arguments? Not to mention the non-trivial cost in code size, that makes exceptions a non-option for embedded use.
- jacobp100 2y agoNoexcept is the biggest scam. It basically wraps every call to a noexcept function in a try/catch, and calls terminate in the catch. Actively harmful for performance
- consteval 2y agoMy understanding of exceptions implementation in C++ is that there's zero performance cost if an exception isn't thrown. Try... catch isn't implemented as a branch, I believe.
- 112233 2y agoThere indeed is zero perfomance cost. Code size cost, however, can be astonishing.
- favorited 2y ago[[noexcept]] was never intended to be a standalone performance boost for arbitrary functions – it was proposed & accepted so container types could make the "strong guarantee" (of the so-called Abrahams guarantees): operations can fail, but failed operations have no side-effects. It allows, for example, std::vector's resize operation to move its contents rather than copying them, iff its element's move constructor is [[noexcept]]. If the move constructor could throw, the items must be copied so the original buffer is unchanged until the entire copy transaction is complete. std::move_if_noexcept <https://en.cppreference.com/w/cpp/utility/move_if_noexcept https://en.cppreference.com/w/cpp/utility/move_if_noexcept> "N3050: Allowing Move Constructors to Throw (Rev. 1)" (2010) <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3050.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n30...> "P2861R0: The Lakos Rule – Narrow Contracts and noexcept Are Inherently Incompatible" (2023) <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2861r0.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p28...> "P2946R1: A Flexible Solution to the Problems of noexcept" (2024) <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2946r1.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p29...>
- Sytten 2y agoRust has problems but certainly not those for sure!
- jampekka 2y agoRust unwraps many such problems.
- _aavaa_ 2y agoWon't somebody please think of the children?
- bfrog 2y agoThose are all features of rust not bugs. Function overloads are evil evil evil. Requiring some mental gymnastics by the reader to pretend to be the compiler what function is actually called. That sucks.
- nneonneo 2y agoRust does support a form of overloading via custom traits. It’s true that you can’t overload e.g. different numbers of arguments (and the lack of default/keyword arguments is especially annoying here), but you can overload a function by having it be generic over a trait argument and then implementing that trait for each overloaded type.
- tialaramex 2y agoI would argue that although this is mechanically equivalent it encourages a much healthier design approach. Take Pattern. One way to look at Pattern is to say that this way we can provide the ad hoc polymorphism of overloading, for functions like str::contains or str::split or str::trim_end_matches -- but as a trait we can see that actually Pattern has discernible semantic properties, clearly a compiled regular expression could be a Pattern for example (and with some feature flags that's exactly correct) In contrast in C++ there are often functions which use/ abuse overloading to deliver separate features in the same interface, expecting that you'll carefully read the documentation and use the correct feature by passing the right type and number of parameters. Constructors are the worst for this, Rust's Vec::with_capacity gets you a growable array with a certain capacity already allocated ready for use -- C++ does not have such a thing - you must make a std::vector and then separately reserve enough space, but it looks like it might have this feature in its constructor as an overload because the constructor has an overload which is the right shape - however that's actually a very different feature, it will fill the std::vector with default initialized objects, rather than reserving capacity for such objects.
- bfrog 2y agoThis is generics. Calling x.some_func() in rust means there is either a type specific function or an impl trait. If more than one option is there rust requires more explicitly calling the types function with x as a parameter. E.g. Something::some_func(x) I’ve never read rust code where I’m entirely guessing which overloaded function is being called. C++ has an entire set of overload resolution rules around this! https://en.cppreference.com/w/cpp/language/overload_resolution https://en.cppreference.com/w/cpp/language/overload_resoluti...
- blastonico 2y agoA proper OO support makes difference for some use-cases. That Serenity OS guy is building a web browser and recently spoke about it. Game developers also complain about the lack if it in Rust.
- vacuity 2y agoAs is typical in Rust-land, lots of talk but implmentations (let alone remotely complete ones) are harder to come by. Partly a procedural and social issue, not just technical queries, which is disappointing. At least delegation (IIUC closer to concatenation as defined in [1]) is currently being implemented, but I can't say how much weight it can actually carry for the OOP efforts. [1] "On the Notion of Inheritance": https://users.csc.calpoly.edu/~gfisher/classes/530/handouts/readings/papers/type-systems/p438-taivalsaari.pdf https://users.csc.calpoly.edu/~gfisher/classes/530/handouts/...
- saghm 2y agoI had the same reaction when reading that part, but it's worth noting that the article used this quote in the context of migration from C++ codebases to Rust, not as a critique of Rust in a vacuum. The next part of the quote clarifies this: > These discrepancies are responsible for an impedance mismatch when interfacing the two languages. Most code generators for inter-language bindings aren’t able to represent features of one language in terms of the features of another.
- kjrfghslkdjfl 2y ago[dead]
- aw1621107 2y agoSubmitted earlier at https://news.ycombinator.com/item?id=41566507 https://news.ycombinator.com/item?id=41566507 but seems it didn't get traction at the time (11 points, 2 comments)
- omoikane 2y agoSee also: https://news.ycombinator.com/item?id=41528124 https://news.ycombinator.com/item?id=41528124 - Safe C++ (2024-09-13, 68 comments)
- pama 2y agoMost recent proposal is here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3444r0.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p34...
- 29athrowaway 2y agoSo everything is unsafe by default, until you turn it on. Great...
- pizlonator 2y agoWeird this got downvoted since this is a big deal. The default matters. So long as the language makes it easy to write unsafe code, people will do it and there will be security bugs.
- blastonico 2y agoThere will always be security bugs. Why do you think otherwise?
- pizlonator 2y agoYes but the point of safe languages is to eliminate the memory safety security bugs, which tend to be the worst and the most common. An unsafe-by-default language that has some safety features isn't enough to fix memory safety.
- deleted 2y ago[deleted]
- uecker 2y agoI don't they are the worst (often hard or impossible to exploit) and also not the most common according to: https://www.cvedetails.com/vulnerabilities-by-types.php https://www.cvedetails.com/vulnerabilities-by-types.php (despite likely easy to find). Memory safety is something which can be solved though, which is what memory safe languages do. Of course, Rust is only an incremental improvement because of unsafe.
- steveklabnik 2y agoThe claim was scoped to the organizations that made the claim: Mozilla, Google, and Microsoft independently claimed that around 70% of their security issues were memory safety related, not that every CVE ever filed was that weighted towards one vulnerability class.
- gdiamos 2y agoGlad to see Sean Baxter is working on this
- worik 2y agoIt is not just memory safety, it is thread safety to. When I was working with Swift that was not available. Swift wraps "fork" in multi syllable function names and half a dozen variations. But it is (was?) still just fork. I do not know about the other languages mentioned. Rust shines on that front. It takes a bit of getting used to, but once you are it is awesome. I loved C++ back in the day. I have left it there, where it belongs. It was a fantastically successful experiment, but move on now.
- ChrisArchitect 2y agoEarlier discussion in September: https://news.ycombinator.com/item?id=41528124 https://news.ycombinator.com/item?id=41528124
- pizlonator 2y agoOr you could use Fil-C++ and get memory safety without any changes. Unlike this proposal, fil-C++ can run real C and C++ programs safely today (including interesting stuff like CPython, OpenSSH, and SQLite). I don’t buy that adding an extension that is safe if you use it will move the needle. But making the language safe wholesale is practical. We should do that instead.
- Svoka 2y agosorry, what's Fil-C++?
- nneonneo 2y agoHis pet project, a garbage-collected variant of C++: https://2024.splashcon.org/details/splash-2024-rebase/3/Fil-C-memory-safety-with-fanatical-C-C-compatibility https://2024.splashcon.org/details/splash-2024-rebase/3/Fil-...
- orion138 2y agoI think it’s this: https://2024.splashcon.org/details/splash-2024-rebase/3/Fil-C-memory-safety-with-fanatical-C-C-compatibility https://2024.splashcon.org/details/splash-2024-rebase/3/Fil-... https://github.com/pizlonator/llvm-project-deluge/blob/deluge/Manifesto.md https://github.com/pizlonator/llvm-project-deluge/blob/delug...
- pizlonator 2y agoYes. More info: https://www.youtube.com/watch?v=xR8y7swDkPU https://www.youtube.com/watch?v=xR8y7swDkPU https://www.youtube.com/watch?v=JRoX9_lXJFg https://www.youtube.com/watch?v=JRoX9_lXJFg
- felipefar 2y agoHard pass on the garbage collector. We don't need that, and the minimal GC support that was in the standard has been removed from C++23.
- netbioserror 2y agoIt's becoming increasingly obvious why Apple and some newer languages like Nim went with RC by default. You get safety, the near-elimination of memory-semantic clutter, AND deterministic performance that's good enough for most use cases.
- pjmlp 2y agoThe only deterministic thing about reference counting is call sites. There is nothing deterministic about how long those calls take, or stack size requirements, especially if they trigger cascade deletions, or are moved into a background thread to avoid such scenarios. Reference counting optimizations slowly turn into a tracing GC.
- marssaxman 2y agoIndeed, "A Unified Theory of Garbage Collection" shows that tracing and counting are duals: https://dl.acm.org/doi/10.1145/1028976.1028982 https://dl.acm.org/doi/10.1145/1028976.1028982
- pornel 2y agoIt's possible, but that's an exaggeration of a degenerate case. RC doesn't mean all data must be tangled into an unknowably large randomly connected web of objects. The behavior is deterministic enough to be profiled and identified if it actually becomes an issue. Identifying causes of pressure on a mark-and-sweep style GC is much more difficult, and depends on specialized GC instrumentation, not just a regular profiler. In practice, you have predictable deallocation patterns for the vast majority of objects. The things that are "young generation" in a GC are the things that get deallocated right away in RC. Time required to deallocate is straightforwardly proportional to the dataset being freed. This can be a predictable bounded size if you're able to control what is referencing what. If you can't do that, you can't use more constant-time alternatives like memory pools either, because those surprise references would be UAFs. I've been there when Apple moved Cocoa from GC to ARC, and the UI stutters have disappeared. It's much more palatable to users to have RC cost happening in line with the work application is doing, than have it deferred to cause jank at unexpected times seemingly for no reason.
- rfmi 2y agoThe concept of a plan compared with a working language A long time C++ programmer that went rusty