[HN Gopher] The year of C++ successor languages
___________________________________________________________________
The year of C++ successor languages
Author : nikbackm
Score : 139 points
Date : 2023-01-02 09:49 UTC (13 hours ago)
(HTM) web link (accu.org)
(TXT) w3m dump (accu.org)
| duped wrote:
| These quotes confused me:
|
| > The Rust language model is based around the so-called borrow
| checker, which tracks the lifetime of all the objects; thus, it
| can detect safety errors at compile-time and does not require the
| use of a garbage collector
|
| > Val solves this problem in an entirely different way: it adds
| restrictions to references, and ensures that nobody can read an
| object while somebody else is allowed to change it.
|
| Because this is exactly what Rust does (forbid multiple mutable
| aliasing). After reading the linked reference (1) the difference
| isn't the program structure but rather that Rust allows
| references in values whereas Val does not, which obviates the
| need for ownership semantics and borrow checking (since there are
| no borrows!).
|
| So my understanding is that both Rust and Val are providing the
| same guarantee to the program (exactly one writer or any number
| of readers to values) but choose to do it in very different ways.
| The interesting thing from the PL design side is that _both_
| implementations leak into the semantics of the language (Rust
| through explicit lifetime annotation, Val through implicit
| references). This is exactly the thing that other newer systems
| language designers seem to gloss over or not understand - you can
| 't slap static analysis onto a compiler (takes like: "we can add
| optional borrow checking in the future") and get the same safety
| guarantees. That will only go so far, since more expressive
| semantics make the analysis impossible.
|
| (1) https://www.jot.fm/issues/issue_2022_02/article2.pdf
| charcircuit wrote:
| Rust didn't originally have borrow checking. It was bolted on
| later.
| estebank wrote:
| This is technically true, but somewhat misleading. The borrow
| checker was added pre1.0, _and_ originally wasn 't known to
| be possible to rely on it exclusively for memory management.
| It was bolted on later, but it changed the language so much
| that if Rust _had_ been 1.x, after the change we 'd be
| calling the current iteration of Rust "2.0". As such, any
| production ready language that attempts the same gambit will
| have to be ready for a painful split that would make the
| Python 3 migration look like a walk in the park.
| fbdab103 wrote:
| The problem with the Python transition is that the language
| is so flexible that it becomes challenging to understand
| if/where/when any code will be impacted by a change. Unit
| testing becomes the static type checker (less so now that
| Python typing is spreading).
|
| For a statically compiled, AOT language, it should be
| easier to migrate the bulk of the code and identify the
| hairy bits. Which is not to say it would be easy or fast,
| but I think Python, Perl or other flexible languages are
| worst case examples for breaking language change
| migrations.
| pcwalton wrote:
| This is technically true, but it was "bolted on" in an
| _extremely_ non-backwards-compatible way+. Tons of APIs had
| to change throughout the ecosystem, which was thankfully very
| small at that point.
|
| This points to what I and others mean when I say that you
| can't just "bolt on" Rust's borrow checking semantics to a
| language that doesn't have one. You technically could add the
| borrow checker to C++ or D or Zig or whatever, but it would
| break so much code that in practice the result would be a new
| language.
|
| Also, a relatively minor point, but still worth mentioning:
| Prior to the introduction of the borrow checker, Rust didn't
| have any way to share memory across multiple threads, which
| made the migration easier as there was no need to update
| multithreaded code to be compatible with the borrow checker.
| But most languages do have shared memory, which would make
| adding a borrow checker to them that much harder.
|
| + Technically it was a gradual process, starting with the
| introduction and slow uptake of unique pointers, then the
| deprecation of @ and migration to Rc/RefCell, and finally a
| long period of plugging soundness holes, many of those fixes
| causing small amounts of breakage as I recall.
| MoscMob wrote:
| It was obviously a click-bait as half-way through the article it
| was clearly written to promote Val, a language practically no one
| has heard of as compared to Carbon and CPP2. The click-bait
| worked. Val looks like a real contender, but now I'm forced to
| ignore it because of the dishonest means by which I was
| introduced to it :)
| MoscMob wrote:
| Oh, another thing. There is nothing about Val that places it as
| a successor of C++ in any way. A C++ successor language starts
| by supporting interoperability with C++, at the source level.
| It didn't even attempt to do this.
|
| This just looks like someone who likes Val wants it to be
| considered as a successor to the language, and not be
| overshadowed by CPP2 and Carbon.
|
| My absolute favorite part of the article, which I'm going to
| steal in the future, is the disclaimer that translates any
| future failure of Val to a success: "if Val dies as a
| programming language but all its ideas are incorporated in C++,
| then I will be delighted."
|
| pahleeze
| michaelmrose wrote:
| Why would a proposed successor language require compatibility
| with c++ source? Logically a successor is one in which you
| write future projects in not one which all existing code must
| compile now else you'd be stuck with the same tech in 2100 as
| 1980.
| de_keyboard wrote:
| I am very bullish on Carbon. Using C++ semantics will make
| switching and integration much easier.
| tialaramex wrote:
| Carbon got one obvious thing right: Culture. The most important
| thing Rust has that C++ doesn't is the right _culture_ and so
| it was correct to focus there very early.
|
| Unfortunately on the technical side it still feels as though
| the intent is to _add_ safety, and IMNSHO that 's not a good
| idea, you want to design safety in from principle, when it's
| layered on later the resulting joints and discontinuities
| always end up causing problems.
|
| On the other hand I am even less confident Cpp2/ CppFront has
| the right idea. There's no sign that Herb Sutter groks the
| culture problem and he's not as focused on safety, preferring
| to see this in part as a vehicle to give all Herb's rejected
| C++ proposals a second chance. As WG21 convenor and as an
| otherwise important person in the C++ community Herb has
| opportunities for Cpp2 that don't exist for Carbon, but if this
| language isn't _enormously_ safer than C++ I don 't see the
| point.
|
| Val certainly has the "from principle" thing. Like Rust there's
| a clear and articulable rationale for why this way to do things
| is safe. I don't know much about Val's culture, and of course
| like these other 2022 "successors" it's very young.
| p0nce wrote:
| But this is exactly why we have C++ in the first place, you
| could port your C code to cfront without doing any effort.
| ChubbyGlasses wrote:
| Same, I was really hopeful that Rust was going to fill the void
| of a modern systems language (been following it since around
| 0.6), but judging by the direction it's been headed the since
| 1.0, state of libs and the ecosystem, and the general community
| sentiment, I've kinda lost hope at this point. To quote
| Carbon's readme, "barriers range from changes in the _idiomatic
| design of software_ " (emphasis mine).
|
| What took the cake for me though, was a post a couple weeks ago
| where people were griping about Go's error handling (if err !=
| nil) when Rust is, at best, no better than Go (e.g if you want
| to add any context to your error), or just objectively worse
| off (? operator moves all your error handling logic to a
| separate, completely different part of your code base).
| galangalalgol wrote:
| Its abstractions and memory safety aren't zero cost though. I
| do wish ffi between rust and c++ was easier, having just fought
| that battle again recently, but I'm not willing to give up
| performance over it.
|
| I think it is telling that carbon came out of google, and rust
| came out of mozilla, but rust is what google is using to make
| android safer, and to create things like kataos.
| surajrmal wrote:
| kataos is a greenfield project. Carbon isn't being created
| for greenfield applications, it's being created to deal with
| maintenance of large existing c++ projects. The carbon repo
| publicly mentions that you are better off using rust
| otherwise.
|
| Using rust in android is indeed interesting, but it's largely
| not hindered by legacy interop with c++ there either. Rust
| has struggled to overcome the challenges with c++ interop in
| chrome. I fear other c++ codebases face this same struggle.
| galangalalgol wrote:
| That makes a lot of sense. It is hard enough on small
| codebases. I'm very familiar with some c++98 behemoths and
| I can't think of any good ways to go about it piecemeal.
| The interop truly is painful.
| flohofwoe wrote:
| It's 'interesting' that the author critices Go for having a GC
| making it 'inappropriate' as a system programming language, but
| then _also_ critices Go for not having exceptions (which are IMHO
| at least as 'inapproriate' for that type of language).
|
| If anything, the latest round of systems programming languages
| which all use error unions instead of exceptions have
| demonstrated quite clearly that exceptions are actually quite
| pointless.
| MoscMob wrote:
| Unless you care about performance. Exceptions have a
| performance benefit in that there's no cost if they are not
| thrown. With returning error values, every caller up the chain
| has to test the error condition.
|
| We've used exactly for this reason to improve performance,
| albeit this was a long time ago.
| okanat wrote:
| System programming doesn't mean kernel and embedded only. The
| huge portion of C++ programs are running in the user space
| because one may still want performance benefits, direct access
| to memory via well-defined struct layout and direct calls to
| the system libraries to get maximum functionality from the OS.
| User space programs can handle exceptions.
| woooooo wrote:
| Sure, but that argument also applies for GC.
|
| I suppose there's a window where you can't tolerate GC pauses
| but can have exceptions allocating memory unpredictably, but
| it's a pretty small window.
| kllrnohj wrote:
| On the contrary, that window is both huge and also you're
| assuming exceptions require expensive allocations _but they
| don 't._
|
| Exceptions are for exceptional situations, so the
| performance impact of them _when thrown_ is rarely of
| concern - both in C++ and in pretty much every other
| language with exceptions. There 's a reason something like
| Java still uses error return values for an awful lot of
| things, after all. But importantly exceptions means that
| you're not doing return value checking for errors that
| rarely happen, which can get expensive on the whole. As in,
| exceptions gives your code a path to both be cleaner _and_
| faster on average - they are really very useful when
| implemented well.
|
| The problem with C++ exceptions has nothing to do with the
| memory allocation cost when thrown (which as mentioned,
| there's other strategies you can deploy than the default of
| just `new`). Rather it comes from the significant impact on
| binary size and the reliance on RTTI.
| pebal wrote:
| You can have GC engine without stopping the world.
| Completely pauseless.
| worik wrote:
| How?
| pebal wrote:
| By analyzing mutators activity. Only the compacting GCs
| have to stop the world.
| MoscMob wrote:
| Not a fan of exceptions, but the runtime cost of exceptions
| vs. the overall runtime cost of GC are not even in the same
| ballpark.
| usefulcat wrote:
| 'Performance benefits' may also include low and/or
| predictable latency. GC is a real problem there. Not
| insurmountable by any means (witness the number of people
| who use java for latency sensitive applications), but a
| problem nonetheless.
|
| Edit: Also, depending on how they're used, exceptions can
| be easier to avoid than GC. IOW, if an exception has been
| thrown at all, you may well have bigger problems than the
| latency cost of throwing.
| zozbot234 wrote:
| Go does have exceptions, via panic/recover. You could argue
| that Rust lacks true exceptions because a Rust panic can be
| configured to be fatal at the whole-program level. (Which is
| actually great for deep-embedded scenarios where even C++ use
| is with no-exceptions support.)
| usrnm wrote:
| Yeah, it's funny how go does have exceptions, but no notion
| of exception safety. Manual mutex locks/unlocks are
| everywhere, and don't even start me on defer, which is just
| terrible
| IshKebab wrote:
| Those are not exceptions by most reasonable definitions.
| Exceptions are a general purpose error handling method. Try,
| catch and all that. They are almost always objects with
| multiple types.
|
| Go and Rust's panic is for unrecoverable errors. They both
| have the ability to catch panics because sometimes you really
| need to do that (e.g. when interacting with FFI or sometimes
| multithreaded code).
|
| They are _not_ exceptions though. I 've seen this myth
| repeated a few times lately (always about Go and not Rust for
| some reason) and I wish it would die.
|
| You wouldn't say C "has exceptions" would you?
| mgaunard wrote:
| There is nothing about exceptions that's inappropriate for
| system programming.
|
| If anything it enables the enforcement of strong invariants and
| leads to better and safer code.
| masklinn wrote:
| > There is nothing about exceptions that's inappropriate for
| system programming.
|
| There's lots about exceptions which is inappropriate for
| system programming, starting from FFI unsafety and the lack
| of signaling to callers (which makes resilient use more
| difficult).
|
| > If anything it enables the enforcement of strong invariants
|
| It doesn't do that.
|
| > and leads to better and safer code.
|
| It only does that in comparison to truly deficient (e.g.
| c-style) error reporting, and that's being generous.
| blub wrote:
| Exceptions have their uses, including in system
| programming. There's in fact nothing about system
| programming which makes a particular error handling method
| better or worse. These are the kind of minor points some
| programmers like to fixate on and then sell as the one true
| way of doing X, while providing no proof and asking
| everyone to trust them, because it worked great in a
| project once for the comment author.
|
| Not letting exceptions escape at API boundaries has been a
| technique for a few decades. It's not rocket science.
|
| Writing exception-safe code is likewise an ancient
| technique by now. Meyers' books which explained such things
| were published in the 90s...
| mgaunard wrote:
| Well it seems you don't understand exceptions. They
| eliminate erroneous states entirely, since the objects just
| don't get created if an error occurs.
|
| The alternative that the parent said was making all of your
| state be a union with some kind of error, and making sure
| all accesses handle the fact the variable might be in a
| erroneous state. That is a huge explosion of possible
| states in your program, and essentially making every
| invariant weak everywhere.
|
| Then FFI, I suppose you mean interfacing with C. Problems
| that arise when interfacing with other programming
| languages are orthogonal to a language's ability to be used
| for system programming. Obviously you wouldn't let an
| exception propagate through some C code, that's forbidden.
| zozbot234 wrote:
| You don't need exceptions for constructors. You can just
| use static factory functions with an error return as Rust
| does, and dispense with constructors altogether.
| jcelerier wrote:
| Can you enforce at compile-time that only such a static
| factory can ever be used for creating the object in Rust?
| This is the whole point of constructors, they cannot not
| be used. Otherwise you're just one commit away of
| creating an object that will not respect the invariants -
| will you even remember to call this specific factory
| function in 6 months?
| zozbot234 wrote:
| You get this by default for any object with non-public
| fields.
| ginsider_oaks wrote:
| Sure, just make the fields of the struct private and you
| can make a public constructor:
|
| https://doc.rust-lang.org/rust-by-
| example/mod/struct_visibil...
| IshKebab wrote:
| Yeah weird question because you can use the exact same
| style in C++.
| jstimpfle wrote:
| To me, constructors are mostly a convenience thing for
| the simple cases where I declare a quick container on the
| stack or similar, and don't want to waste keypresses to
| have it constructed. And to me it's a question of, what
| does the language want to be -- maybe this kind of code
| is better served by languages like C#.
|
| There are various practices that handle the problem of
| having to call a specific function to get an object in a
| specific state, without requiring language support. You
| can make the function that should be used stand out in an
| obvious way. You can hide the definition of a structure,
| which very
|
| If it is the whole point of constructors to _guarantee_
| that the object is in the right state, it could be the
| best argument why Rust does not have constructors.
| Programming is chock-full of "this must be called only
| by that or in this or that context..." and practically
| speaking, only a part of them can be handled by language
| objects and construct/deconstruct semantics.
|
| Plus, C++ gives enough escape hatches to get not-
| constructed objects or to un-construct objects without
| them going out of scope. These guarantees that C++
| provides (but not really), they require a ton of baggage
| like ridiculous initializer lists or 17 different kinds
| of constructors (to the point where it's sometimes almost
| impossible to tell which will be called), or requiring an
| out-of-band mechanism (exceptions) to signal construction
| failure.... that's not worth it in my book.
| mgaunard wrote:
| Enjoy your combinatorial explosion of states.
|
| It's not equivalent at all.
| Yoric wrote:
| Could you elaborate? Rust manages pretty well without
| constructors and I'm nearly certain that it doesn't have
| any kind of combinatorial explosion of states. Same for
| ML-family languages.
| mgaunard wrote:
| See other parts of the thread.
| masklinn wrote:
| > Well it seems you don't understand exceptions. They
| eliminate erroneous states entirely, since the objects
| just don't get created if an error occurs.
|
| Error sum types do the exact same thing.
|
| > The alternative that the parent said was making all of
| your state be a union with some kind of error, and making
| sure all accesses handle the fact the variable might be
| in a erroneous state.
|
| Which is a non-issue as it is lifted to the type system.
| The type system will not let you forget about that.
|
| > That is a huge explosion of possible states in your
| program, and essentially making every invariant weak
| everywhere.
|
| You get an error state added to a given value, which you
| also get via exceptions, except implicitly and without
| notification of the additional state.
|
| Type-safe error values also provide simpler error
| handling and recovery in many case, because they don't
| _require_ split-path handling.
|
| > Then FFI, I suppose you mean interfacing with C.
| Problems that arise when interfacing with other
| programming languages are orthogonal to a language's
| ability to be used for system programming.
|
| It very much isn't, part of the system programming
| workload is to provide reusable components.
|
| > Obviously you wouldn't let an exception propagate
| through some C code, that's forbidden.
|
| And rarely if every checkable statically, hence unsafe.
| mgaunard wrote:
| The problem is not "forgetting about it", it's that it
| increases the possible values of your working set.
|
| If you have 3 variables, each of which can be in 10
| states, that's 10^3 states your program can be in.
|
| If instead you have 11 states because all your variables
| are actually unions with an error, that's 11^3 states
| (assuming all error states are equivalent to a single
| state).
|
| Now in practice it's even worse since what you care about
| isn't the possible states of your values, but rather how
| many different paths you have in your control flow to
| handle them.
|
| Then you're comparing 1 (none of my values are in an
| erroneous state) with 2^3=8 (any of my values can be in
| an erroneous state or not).
|
| What exceptions do is enforcing that your working set
| does not have to encode any erroneous states, preventing
| the combinatorial explosion of states, which of course is
| a net win, there isn't really any valid argument that can
| be made against it.
|
| Where people are debating is that sometimes you do want
| errors to be part of your working set, in which case you
| shouldn't use exceptions. But choice is difficult for
| some, especially those seeking absolute doctrines.
|
| > It very much isn't, part of the system programming
| workload is to provide reusable components.
|
| That's already somewhat dubious, since a lot of system
| programming tasks are really purpose-built for a usecase
| or for specific hardware, and regardless, there is
| nothing about that which has anything to do with
| interfacing with C.
|
| I do a lot of system programming and I write it all in
| C++, which has a lot of advantages over C beyond
| exceptions.
| deschutes wrote:
| This doesn't track at all for me. Rust provides strong
| guarantees around accessing discriminated unions. The net
| effect of which is that the code you write has the
| "railway style" error handling that you get with
| exceptions in the trivial case (propagate the error). It
| even has a convenient syntactic shorthand for this `?`.
|
| In non-trivial cases they are equivalent too. For
| example, collections need to maintain at a minimum a
| valid state in the presence of types with exception-
| throwing (fallible) constructors. This is a mess with or
| without exceptions in basically the same way. It's such a
| mess that the C++ standard allows for unspecified
| behavior of `std::vector::push_back` if the contained
| type has a throwing move constructor. Throwing move
| constructors are of course ridiculous but nonetheless
| allowed.
|
| And that I would say is the biggest flaw with exceptions:
| they presume the fallibility of everything by default.
| This is not only brain damaging, it actively creates
| situations where there are no good options.
| jstimpfle wrote:
| Your reasoning is off. You don't have "10^3" states if
| you always unwrap the return values at the call sites
| (which implies returning if it fails). It's literally the
| same as exceptions, just that the errors get encoded by
| (re-)using the type system. You'll have the exact same
| types for your local variables -- the only difference
| being that you would put a '?' (or similar) after
| function calls, to unwrap the return values.
|
| The advantage of this ADT approach is that you _can_
| store error unions more permanently when it makes sense.
| It is _not_ additional syntax, unlike exceptions. In that
| sense ADTs are the simpler approach of the two. If there
| is any "explosion of complexity", then it is
| _exceptions_ where you get that -- because you have to
| express your code using multiple mechanisms (types vs
| exceptions), and possibly have to switch between the two
| when refactoring.
|
| I say that as someone who doesn't think highly of either
| approach. In my view, plain error values are fine, there
| isn't any clever language solution needed. If you find
| yourself checking return values a lot (as opposed to
| storing error values in handles and checking them at
| strategic locations), that can hint an architectural
| problem.
| mgaunard wrote:
| By unwrapping and returning, you're creating another path
| down the control flow of your program, which also
| propagates to your callee, since you have to return an
| error.
|
| Exceptions don't do that, they stop the flow entirely,
| then match it to a point arbitrarily higher on the stack,
| and resume after that whole sub-tree has been destructed.
|
| They're also much more efficient than branching and maybe
| returning on the result of every single function call.
| jstimpfle wrote:
| I don't see a reason why the compiler couldn't implement
| error-sum return values the same way that exceptions are
| typically implemented (the way you describe).
|
| (I don't see why it should, either. The blanket
| "efficiency" argument is unconvincing to me).
|
| Ok, I see one reason: The programmer might want control
| which implementation is used. That would require an
| additional mini-feature in the language syntax/function
| types. But this still wouldn't be an argument for a whole
| different syntax and forced separate code paths as
| required for traditional exceptions. And it's theoretic
| anyway -- I don't think it's important to give the user
| this "control".
| masklinn wrote:
| > Exceptions don't do that
|
| Exceptions actually do that, except hidden and
| unsignaled.
|
| > They're also much more efficient than branching and
| maybe returning on the result of every single function
| call.
|
| Not when actually taken.
| fckgnad wrote:
| >What exceptions do is enforcing that your working set
| does not have to encode any erroneous states, preventing
| the combinatorial explosion of states, which of course is
| a net win, there isn't really any valid argument that can
| be made against it.
|
| A combinatorial explosion of states is not a bad thing.
| Integers in C++ for example have 4294967296 possible
| states. Programming is not descending into complete chaos
| just because one of the fundamental types has more
| possible states then the human brain is capable of
| handling.
|
| You're describing using exceptions as a catch all fail-
| safe. It's isomorphic to the the "else" statement in your
| standard if-else structure which is one of the techniques
| people use to handle the 4294967296 possible states of
| int. See example code below on this amazing technique I
| use to deal with 4294967296 possible branching
| possibilities: if(x == 0){ //
| do something } else { //handle all
| 4294967295 other states. }
|
| >Where people are debating is that sometimes you do want
| errors to be part of your working set, in which case you
| shouldn't use exceptions. But choice is difficult for
| some, especially those seeking absolute doctrines.
|
| In every other engineering field you do want this as part
| of your design. You want to know about every possible
| state your system can be in and handle the states
| explicitly. Unknown states that are not explicitly
| encoded into an engineering design is typically a Bad
| thing.
|
| That is not to say you should design your system and not
| acknowledge the possibility of an unknown state. You need
| fail-safes like exceptions to handle these unknown
| states. But make no mistake, it's not good to have fail-
| safes regularly executing to catch a bunch of states you
| failed to encode into your system.
|
| A good example of this is corrected design of the MCAS on
| the boeing 737 max. The MCAS should not use a fail-safe
| handle the crash modes we are now well aware about. The
| MCAS should explicitly be encoded with our knowledge
| about the new possible error modes. I certainly don't
| want to sit in a plane where this hasn't been done.
|
| I will also say that much of programming doesn't need the
| level of safety other engineering products need. Shipping
| products faster at the cost of quality is something
| unique to software as the quality can be improved AFTER
| shipping, so that is not to say your way of using
| exceptions to catch unknown states (or states not
| explicitly encoded into the system) is completely wrong;
| but is certainly not best practice or ideal.
| masklinn wrote:
| > If you have 3 variables, each of which can be in 10
| states, that's 10^3 states your program can be in.
|
| > If instead you have 11 states because all your
| variables are actually unions with an error, that's 11^3
| states (assuming all error states are equivalent to a
| single state).
|
| > Now in practice it's even worse since what you care
| about isn't the possible states of your values, but
| rather how many different paths you have in your control
| flow to handle them.
|
| You're really demonstrating that you have no clue about
| the subject and refuse to think about it.
|
| If the current function does not deal to specifically
| deal with erroneous results (aka it would be a
| passthrough for exceptions) then it unifies the error
| states into one, by either pruning their branches through
| early-returning, or unifying the triplet of results into
| a result of triplet.
|
| Hence you don't have 11^3 states but 10^3 + 1.
|
| > What exceptions do is enforcing that your working set
| does not have to encode any erroneous states, preventing
| the combinatorial explosion of states, which of course is
| a net win, there isn't really any valid argument that can
| be made against it.
|
| The problem is that none of that is actually true, you're
| literally inventing combinatorial explosions which
| effectively don't exist.
|
| Unless they would have to in all cases at which point
| exception would lead to a significantly worse
| combinatorial explosion, because exceptions would not
| allow representing the product of 11 states as just that,
| and instead would need 20^3 states as every possible
| value would have to be paired with two error states,
| success and failure.
|
| > That's already somewhat dubious
|
| It really is not.
|
| > there is nothing about that which has anything to do
| with interfacing with C.
|
| The C (or system) ABI is the linga franca of inter-
| language communication, unless you decide to pay for a
| network cost.
|
| > I do a lot of system programming and I write it all in
| C++, which has a lot of advantages over C beyond
| exceptions.
|
| And plenty of drawbacks as well.
|
| But if all you know is C and C++ and you see the entire
| world through that lens, I can see why you're missing
| most of the field, you're essentially blind.
| mgaunard wrote:
| Exception prevent the control flow from continuing, which
| prevents the creation of those states which happens
| further down.
|
| I find your tone too inadequate to engage further with
| you though.
| zozbot234 wrote:
| > What exceptions do is enforcing that your working set
| does not have to encode any erroneous states
|
| You do that by having types that encode a guaranteed non-
| erroneous state. It's not like exceptions are doing
| anything all that different, they're just trying to
| establish that guarantee in a language where variant
| record types and pattern matching are not first-class
| facilities.
|
| This is something where C and C++ actually regressed from
| PASCAL, which did have support for variant records.
| mgaunard wrote:
| A variant does not make that guarantee, it just
| segregates it.
| jstimpfle wrote:
| Same for exceptions really. Exceptions don't give any
| guarantee of non-erroneous state. The guarantees that
| you're talking about actually come from how construction
| and deconstruction work in C++ (note how it plays with
| early returns just fine, no exceptions needed). And these
| construction semantics can be implemented with variant
| types as well, it's completely unrelated.
| mgaunard wrote:
| The prevent the control flow from continuing in that
| direction, which prevents those variables from ever
| existing.
|
| Early return is nothing like exceptions. Early returns
| needs to return something which passes the problem to
| someone else. It's also a choice to do it at all.
| anonymoushn wrote:
| > If instead you have 11 states because all your
| variables are actually unions with an error, that's 11^3
| states (assuming all error states are equivalent to a
| single state).
|
| This isn't what actually happens though, what actually
| happens is that people declare local and member variables
| that are the_type_i_actually_want instead of Result<err,
| the_type_i_actually_want> and bubble up their errors like
| they would exceptions. So they get the benefits that
| you've claimed, but they don't need to pay the runtime
| cost of not-thrown exceptions that C++ users have to pay,
| they don't have to use external tools to tell them that
| functions they're using can throw exceptions, and they
| don't have to enjoy the wonders of Java where checked
| exceptions in function signatures regularly prevent the
| use of streams.
| menaerus wrote:
| You're conflating recoverable errors (Result in Rust,
| status codes or std::expected in C++) with the non-
| recoverable errors (panic in Rust, exceptions in C++).
|
| If we were to compare Rust panics vs C++ exceptions, then
| handling of Rust panics is much less flexible. From what
| I understand, it's essentially a std::abort and it can be
| hardly used otherwise, which is only a subset of how C++
| exceptions can be commonly used too.
|
| If we were to compare Rust Result vs C++ std::expected,
| they boil down to pretty much the same with the
| difference of Rust requiring the call-site to
| unconditionally check for the return value. That may or
| may not be preferable in every situation.
|
| > they don't need to pay the runtime cost of not-thrown
| exceptions that C++ users have to pay
|
| Had this been true, which in 99% of cases it isn't unless
| you can support your claim, do you mind sharing how Rust
| implemented their zero-cost panics?
| ginsider_oaks wrote:
| They're not conflating.
|
| mgaunard says:
|
| > The alternative that the parent said was making all of
| your state be a union > with some kind of error, and
| making sure all accesses handle the fact the > variable
| might be in a erroneous state.
|
| This is exactly what `Result` is in Rust. While I haven't
| used Rust, it seems that panics are generally discouraged
| and only used as a last resort whereas exceptions are
| more commonly used in C++ and Java.
| menaerus wrote:
| Yes, they are. They are basing their argument by
| comparing Rust Result against C++ exceptions in the
| context of general error handling whereas I pointed out
| that there are actually two classes of errors and both of
| which are addressed their own appropriate mechanisms in
| both Rust and C++.
|
| What parent comment tried to (wrongly) imply, and your
| comment as well, is that exceptions in C++ are (commonly)
| used as a control flow mechanism. And they are not.
| mgaunard wrote:
| There is no cost to exceptions that are not thrown.
|
| On the contrary, the approach you describe introduce a
| lot of overhead, since it affects all code paths, the
| function call ABI, jumps after every function call etc.
|
| Also in C++ you have operators that are integrated in the
| type system and are resolved at compile-time to know
| whether an given expression can throw an exception or
| not. Do not confuse C++ with Java.
| marcosdumay wrote:
| Yeah, that's why exceptions were created back then. They
| got rid of a lot of extraneous branches in exchange for a
| small, nearly constant cost on your function calls.
|
| But with decades gone, things changed. That constant
| costs is relatively not so small anymore, and those
| branches are much cheaper now.
| anonymoushn wrote:
| > There is no cost to exceptions that are not thrown.
|
| This is not true for a variety of reasons, but the main
| ones are maybe missed optimizations and otherwise-
| unnecessary spills of objects into memory so that their
| destructors may be called.
|
| > Also in C++ you have operators that are integrated in
| the type system and are resolved at compile-time to know
| whether an given expression can throw an exception or
| not.
|
| Maybe if you only have a single TU or LTO? In general any
| function from another TU can throw an exception so you
| don't have this.
| mgaunard wrote:
| > This is not true for a variety of reasons, but the main
| ones are maybe missed optimizations and otherwise-
| unnecessary spills of objects into memory so that their
| destructors may be called.
|
| The missed optimization opportunity you describe only
| affects the Windows ABI, designed in 1989.
|
| > Maybe if you only have a single TU or LTO?
|
| Whether a function can throw or not is part of its
| signature.
| kllrnohj wrote:
| > You get an error state added to a given value, which
| you also get via exceptions, except implicitly and
| without notification of the additional state.
|
| This is incorrect. With error return values you're adding
| a branch to _every function call_ which is quite
| expensive on the whole. You 're adding i-cache pressure &
| you're adding branch prediction pressure.
|
| Exceptions in nearly every language that supports them
| (including C++) don't go through return values at all.
| Rather when thrown the stack is walked to find an
| exception handler. So exceptions are more expensive to
| throw than return values, but completely free when not
| thrown unlike return values.
|
| > It very much isn't, part of the system programming
| workload is to provide reusable components.
|
| There's absolutely no issue with exceptions & library
| boundaries in general. Statically checked exceptions also
| exist (see Java - although there's a big debate on if
| that's a good idea or not, but also see https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2019/p07... ), I'm not
| sure why you're arguing as if they don't.
| worik wrote:
| > So exceptions are more expensive to throw than return
| values, but completely free when not thrown unlike return
| values
|
| Is that true though? Placing exception handlers in the
| stack, so examining every stack frame for one, is
| equivalent to testing the sum type to see if it is in
| error state, surely?
|
| My disclaimer: I know very little about compilers, so
| this is an actual question.
| mgaunard wrote:
| You don't put exceptions handlers on every frame.
| jcelerier wrote:
| Signaling to caller has already been shown to be a complete
| mistake with java checked exceptions, I don't understand
| why people persist with this. It makes for ridiculous code.
| jstimpfle wrote:
| Are you saying that "not signaling" has not been shown to
| be a total maintenance nightmare as well?
|
| Because the only way it is not ever a maintenance
| nightmare is when you really don't care that function
| execution could be aborted at any point without any
| visual clue. And the only way I can see you wouldn't care
| is when you go 100% in to everything-context managed /
| RAII or similar. And that again, sorry but I can't be
| arsed to prematurely rip everything into little pieces
| like that. It makes for terrible code IMO.
| masklinn wrote:
| > Signaling to caller has already been shown to be a
| complete mistake with java checked exceptions
|
| It's not signalling which has been shown to be a complete
| mistake, but checked exceptions, more specifically as
| implemented by Java.
|
| Signalling works just fine and is actually rather
| enjoyable when done well.
| kllrnohj wrote:
| Can you point to an example? What has "signaled
| exceptions"?
| the_why_of_y wrote:
| This may be true for checked exceptions, but certainly not
| for unchecked exceptions.
| mgaunard wrote:
| All exceptions must be caught to satisfy lifetime
| invariants in multi-threaded contexts.
| the_why_of_y wrote:
| Of course. With unchecked exceptions, it's impossible to
| statically check that this is the case, so for system
| programming they are not an appropriate error handling
| mechanism.
| josefx wrote:
| > With unchecked exceptions, it's impossible to
| statically check that this is the case
|
| Aren't checked exceptions just a hint to the programmer?
|
| Also I don't see why static analyzers shouldn't be able
| to map out which functions throw which exceptions without
| having hints in the language itself, the information is
| in the code.
|
| Lastly if the goal is as simple as catch all exceptions,
| can't you just enforce a catch all on the top level?
| mgaunard wrote:
| None of system programming can be statically checked,
| since it's mostly about I/O with weak memory models.
| Yoric wrote:
| Caveat: we may be using different definitions of "system
| programming". The one I'm using is code that is fairly
| close to the system, i.e. will call into libc or into the
| kernel/libSystem/etc. as well as calling more-or-less
| directly into a bunch of system-specific .so/.dylib/.dll.
|
| In my experience as a system developer, you need to
| invest some time into understanding the invariants
| expected/promised by the libraries you're calling, but
| many of them map nicely to static types. Of course, if
| you're implementing e.g. a IPC or RPC layer, you need to
| deserialize (and validate along the way) your inputs, but
| there are very few systems that do not need to do that
| regardless.
| mgaunard wrote:
| Code that uses libc is just normal code.
| Yoric wrote:
| I don't disagree :)
| codeflo wrote:
| > enables the enforcement of strong invariants
|
| My experience has been the opposite. Ensuring exception
| safety in a type that has nontrivial move/copy operators
| (that is, a type that for whatever reason can't follow the
| "rule of zero") is often a research-level problem. Not having
| to worry about that in Rust is such a breath of fresh air.
| mgaunard wrote:
| Enforcing basic exception safety is trivial, you just have
| to follow very simple rules.
|
| Enforcing strong exception safety might require some
| thought, but it's definitely not "a research-level
| problem".
|
| In any case either of these is miles easier than satisfying
| the Rust borrow checker, unless you use the cop-out of
| (A)rc.
|
| Regardless, how the invariants of your objects are
| maintained in case of operation failure is something you
| should be thinking of regardless of the language.
| Yoric wrote:
| > Enforcing basic exception safety is trivial, you just
| have to follow very simple rules.
|
| That's not my experience as a C++ developer in a complex,
| cross-platform, application, which needs to:
|
| 1. interact with C;
|
| 2. operate with an event loop;
|
| 3. operate/interact with a GC;
|
| 4. interact with non-trivial system libraries (e.g.
| Direct3D, Vulkan, ...)
|
| > In any case either of these is miles easier than
| satisfying the Rust borrow checker, unless you use the
| cop-out of (A)rc.
|
| The borrow checker is indeed complicated. I'm not sure
| how you define "satisfying", though. As for (A)rc, it can
| definitely be interpreted as a "cop-out" or as delaying
| optimization until you actually have good reasons to
| believe that you need it.
| mgaunard wrote:
| I'm sorry to hear that you haven't been successful in
| using C++ features to their full potential in
| environments tightled coupled with C libraries.
| Integration with C or C-like code usually requires some
| effort if you want to be able to use exceptions that
| could propagate through C.
|
| I do not offer consulting but can refer you to people who
| do.
| Yoric wrote:
| Thanks. Do you think they'll have time to rewrite
| Firefox? :) (or Chrome, which encounters the same issues)
| moloch-hai wrote:
| Maybe switch to the SerenityOS browser.
| Yoric wrote:
| I'll be sure to suggest that to Mozilla and Google :)
| cyber_kinetist wrote:
| No, you do have to worry about that too in Unsafe Rust if
| you want to achieve memory safety. (If you're writing only
| Safe Rust then probably not, but then that also applies to
| C++ if you're adhering to the rule of zero and extensively
| use the STL containers for all your memory allocation
| needs.)
|
| Exceptions _do_ exist in Rust, and you _do_ need to catch
| it explicitly at the FFI boundary. [1][2] And the
| programmer needs to take care their abstractions are safe
| with stack unwinding when writing any kind of unsafe code
| in Rust. (For an example: [3])
|
| [1] https://doc.rust-lang.org/nomicon/unwinding.html
|
| [2] https://doc.rust-
| lang.org/std/panic/fn.catch_unwind.html
|
| [3] https://doc.rust-lang.org/nomicon/exception-safety.html
| kajaktum wrote:
| >If anything it enables the enforcement of strong invariants
| and leads to better and safer code.
|
| How?? If a container have `get(K)->V` and `remove(K)->V` then
| how does it preserve this invariant? This is an impossible
| contract to satisfy once you try to push once and pop twice.
| The container is promising you something that it can't
| satisfy, I would rather have a container that's honest with
| `get(key)->Maybe(value)`.
| mgaunard wrote:
| You have it backwards, the strong invariant is that you
| have a container and not a maybe container.
| alphanullmeric wrote:
| Exceptions have zero cost when not thrown, apart from being
| included in the binary
| pornel wrote:
| A mere possibility of a function throwing an exception is a
| side effect that an optimiser can't ignore, and this inhibits
| many types of optimisations involving code motion. This is
| for example the main cost of bound checking in Rust, not the
| branches.
| AshamedCaptain wrote:
| I always hear this, but these days entire Linux distros
| build with -fasynchronous-unwind-tables which has much more
| overhead than C++ exceptions, since now you have to provide
| an unwind path for every single instruction boundary. Even
| Chrome does it (for "accurate stack traces") when otherwise
| they're on the no exceptions camp. Using this option also
| completely subsumes the otherwise negligible cost of C++
| exceptions, which only affects non-inlined function calls
| (which already limit code motion anyway).
| dmitriid wrote:
| > the latest round of systems programming languages which all
| use error unions instead of exceptions have demonstrated quite
| clearly that exceptions are actually quite pointless.
|
| What they have actually shown is that error unions are not a
| panacea and a pain to handle manually. And that hardcore
| killing your app in the presence of even the tiniest of
| unhandled errors isn't suitable for any programming, especially
| systems programming.
|
| That's why Rust ended up introducing try!, ? and catching
| panics. Go also added panic recovery.
| zozbot234 wrote:
| try! and '?' is just a syntactic convenience for passing
| error returns to the caller. It's not even monad-like because
| the outer code still has to wrap the happy path return with
| Some(...) or Ok(...), which wouldn't be the case with an
| actual monad.
|
| (I.e. it's quite different from what was done with "async fn"
| support, where the Future return type is hidden via the
| 'async' specifier and the control flow is totally changed.)
| dmitriid wrote:
| > try! and '?' is just a syntactic convenience for passing
| error returns to the caller.
|
| Indeed. Because manually handling those are a pain in the
| butt. So they made a shortcut. That still needs to be
| handled by someone up in the hierarchy. Exceptions in all
| but name.
|
| > It's not even monad-like because
|
| No one cares whether it's monads or not.
| tialaramex wrote:
| The crucial thing about Try (the ? operator) is actually
| pretty easy to see if you look at the Trait which makes
| it go:
|
| The result of the branch method on Try is
| ControlFlow<Self::Residual, Self::Output>
|
| Try isn't stable, but ControlFlow is. They've reified the
| control flow! Rust has a type which represents the idea
| of a decision whether to return early. This is in my
| opinion pure genius, and it happened almost by accident.
| It seemed natural at first that the decision to return
| early is manifested directly in Result, but it isn't.
| That's the insight. Failure and returning early are
| distinct ideas, and we might just as easily want to
| return early in consequence of _success_ as failure.
|
| In separating "Success versus Failure" from "Return early
| versus keep going" Rust gets a lot more value here than
| is encapsulated in C++ Exceptions.
| moloch-hai wrote:
| > Exceptions in all but name.
|
| But with added space and branch-predictor overhead in
| every call site, and runtime overhead in every call
| event!
| Yoric wrote:
| I'd argue that they have demonstrated that:
|
| - unions are not a panacea and are a pain to handle manually,
| as you wrote;
|
| - but with a little syntactic sugar, they turn out to work
| really, really well, much better than C++-style exceptions.
|
| There's something to be said for OCaml-style exceptions,
| which are actually even closer to zero-cost, but I wouldn't
| call OCaml a systems programming language.
|
| Writing this as one of the persons who advocated for try!
| around the time of Rust 0.4 :)
| uwuemu wrote:
| For me 2022 was the year of learning C++ (background in C# and
| Php) and I must say that after being intimidated by the language
| (extensive use of pointers, stack/heap compile/run time
| allocation and deallocation, templates) for years, when I really
| took a proper look and got some practice, it is an incredible
| language. The command an control you get by working with memory
| and the system on a lower level is amazing. I found it so much
| easier to learn (once I got the grasp of the cpp way of doing
| things) than something like Rust. All the higher level advanced
| concepts work and flow beautifully as you'd expect based on the
| lower level of the language... and the amount of "magic" is kept
| to minimum... you can just open std and look at what is being
| done and it all males sense. I understand why people want to move
| from C++ but as a newbie in this language, I find it amazing.
| UncleOxidant wrote:
| > All the higher level advanced concepts work and flow
| beautifully as you'd expect
|
| Are we talking about the same language? C++?
| wiseowise wrote:
| Also started learning C++ last year. Can concur.
| eps wrote:
| > I find it amazing.
|
| This shall pass :)
|
| The main beef with C++ is that it's just a mass of every single
| feature possible held together with some goo. It works, but
| there's no grace and elegance.
| fwsgonzo wrote:
| It is really only the fact that everyone writes C++ a little
| bit differently. From the outside this looks like C++ is a
| mess, but in reality, you can pick and choose what you like,
| and that's exactly what people do. You can be as safe or as
| unsafe as you need to be. That, of course, also opens the
| door for beginners to accidentally write unsafe code for a
| long time before they know all the footguns.
|
| I think the real dent in C++ comes from wholesale
| improvements to languages by adding package management, one-
| liner built-in toolchains, built-in testing and build system.
| I could write paragraphs about why this is a good thing but
| we all know why.
|
| CMake is making an effort in making it easy to fetch content
| for your build system, including Git repos, so there are
| paths to take today, but you need to learn a lot of separate
| things just to get started with C++. What are the chances
| that a beginners C++ tutorial gives you all the best
| practices in a way that a newer language does by default?
|
| Learning C++ will probably look like the experience of using
| a web server with poor defaults, a strange configuration
| language, and thousands of different tutorials detailing a
| 20-year period of changes.
|
| If I were to host content today I would not use Apache or
| nginx - I would probably start with Caddy and go from there.
| moloch-hai wrote:
| Generally it is the people coming from C or C++98 who write
| unsafe code. People picking up C++20 start out using it
| safely, and continue.
| menaerus wrote:
| > improvements to languages by adding package management,
| one-liner built-in toolchains, built-in testing and build
| system. I could write paragraphs about why this is a good
| thing but we all know why.
|
| Not everybody shares this sentiment. I think it's great to
| be able to get yourself up and running quickly, and start
| dabbling with the language immediately, but I also think
| that at the same it is not so great because there is no
| "one size fits all purpose". I, for instance, happen to
| value the latter more than the former.
| celrod wrote:
| FWIW, I also wrote a lot of C++ for the first time last
| year, and found the lack of
|
| > package management, one-liner built-in toolchains,
| built-in testing and build system
|
| To be by far the least pleasant things about the
| language, especially C++20 where things like concepts are
| wonderful to use, but then you can't rely on having a
| toolchain that actually supports it. The most recent
| xcode/Apple Clang do not, so on Mac you need to find your
| lib(std)c++ elsewhere.
|
| It'd be great if there were at least some way to make
| this easy, e.g. to create a virtual environment that sets
| all paths correctly. Or if the compiler from a toolchain
| would default to using its own headers and libraries
| instead of defaulting to the system's. (I assume they
| don't for a combination of historical reasons, and that
| you'd then you may be unable to link to system libraries
| due to things like mismatched std libraries).
|
| Preferably, there would also be a GitHub Action to do it
| to make CI easier to set up.
| jcelerier wrote:
| ... are there really people who go into their office job and
| think "oh this tool I'm using needs more grace or elegance?".
| If I had a carpentry company and my employees complained that
| their hammers and nails weren't elegant enough... I'd quickly
| look for new employees.
| pjmlp wrote:
| While C++ is the one with more features, the same can be said
| about Java 20, Python 3.11, C# 11, Haskell 2021,... when
| comparing against their version 1.0.
| masklinn wrote:
| The problem of C++ is that its features are not just
| additive. In most languages, if you take two features and
| use them both, they add up. In C++, they may actively
| interfere with one another.
| moloch-hai wrote:
| I doubt you can present even just a single example. All
| languages' features combine multiplicatively.
| masklinn wrote:
| The rule of 0/3/5 literally exists because the default
| behaviours misinteract with any override of the rest.
| moloch-hai wrote:
| Yeah, that doesn't qualify. It is all one feature:
| override copy or move semantics, and there is a
| prescribed way to do it. Rule of zero, of course, is the
| norm, and is an extremely beneficial interaction.
|
| 2nd try?
| kllrnohj wrote:
| > In most languages, if you take two features and use
| them both, they add up.
|
| Java nio vs. io
|
| Java Reader vs. InputStream (not necessarily the
| interfaces themselves, but the redundancy between them eg
| should I use InputStream -> BufferInputStream ->
| InputStreamReader? Or InputStream -> InputStreamReader ->
| BufferedReader?)
|
| Java float[] vs. ArrayList<Float> vs Vector<Float> vs.
| FloatVector vs. FloatBuffer - just how many ways can we
| describe "a linear allocation of numbers" in Java at this
| point? And they're still adding new ones!
|
| So no, other languages don't just magically handle this
| more gracefully than C++ does. If a language is
| successful, it will either suffer from this or it'll
| stagnate - it's the natural consequence of preserving
| backwards compatibility while adding new features &
| capabilities.
| wiseowise wrote:
| Don't forget Date/Time classes. Absolute mess.
| pjmlp wrote:
| I gather you don't have much use of the latest versions
| of the languages I mentioned, in all of them I can think
| of examples that don't add up.
| dagmx wrote:
| I'd be curious which Python features don't work together?
| nurettin wrote:
| C++ gets special treatment for some reason when it comes
| to backwards compatibility and modern language features.
|
| Oh no! we got threads and timers! C++ recognized the
| existence of file systems and regular expressions, woe I
| say! Polymorphic lambdas? I've got std::bind1st haha!
| Wait, not atomic! Anything but that!
| singularity2001 wrote:
| It's all sunny until valgrind / asan fail to reveal the source
| of an elusive "Uninitialised value was created by a stack
| allocation"
| Markstar wrote:
| Yep, I totally agree. I took a long break from C++ after I
| learned the basics at university. I got into it again one and a
| half years ago when I needed _really_ fast code and I was
| pleasantly surprised how easy it is. I 'm exclusively using
| smartpointers and I very rarely run into scenarios where I have
| problems with memory leakage, etc.
|
| Since I had written the prototype in Java and now interface
| with Javascript/Typescript, I'm really amazed how clean and
| well-reasoned my C++ side of the program is. So yeah, I'm also
| really happy with the state C++ is in right now.
| bjackman wrote:
| I work on a C++ project and actually sympathise with this
| sentiment. It's a massive strategic risk to our org, we pay a
| consistent tax in reduced productivity and stupid bugs, and I
| will advocate migrating away as soon as we have a suitable
| destination (Carbon looks promising).
|
| But, I do also really enjoy writing C++! I'm optimistic that
| writing Carbon/Val/whatever ends up gaining traction in the
| industry will be just as good though.
| e145bc455f1 wrote:
| Are you using legacy C++ or modern C++?
| meindnoch wrote:
| Why was this comment flagged? C++ haters... really?
| fckgnad wrote:
| It doesn't deserve a flag, but the sunny optimism feels
| delusional. It's obvious that C++ has problems and there's
| obvious red flags like why someone like Linux Torvalds
| vehemently hates the entire language.
|
| The poster addresses none of this and has a overly positive
| attitude towards C++. There's obvious nuance on this topic
| that the post fails to address and he instead just preaches
| to a biased choir. At least he admits he's a beginner.
| alphanullmeric wrote:
| [flagged]
| Yoric wrote:
| All the Rust programmers I know are really happy to discuss
| the limitations and shortcomings of the language. It's
| possible that the problem you're pointing at comes from the
| word "reddit" rather than "rust" :)
|
| Note: I've looked at the parent's comment history. The only
| one of them which was not a troll against Rust was a troll
| against the EU.
|
| I'll stop feeding the troll.
| alphanullmeric wrote:
| I'll correct my comment.
|
| Rust programmers are sensitive people that cannot accept
| criticism of their language. Just look how personal they
| get on HN when a post puts the borrow checker in a
| slightly bad light.
| umanwizard wrote:
| Nobody is "getting personal" on this thread.
| umanwizard wrote:
| We use rust full time at my job, and griping about the
| language's rough edges is a common pastime. The fanboys are
| loud and obnoxious but I can assure you they don't make up
| the whole rust community.
| zozbot234 wrote:
| Rust has its rough edges, but what people do is gripe
| about those in language development spaces. Then the
| rough edges get filed down in the next release trains, or
| sometimes the next language edition. In C++ they are
| totally unfixable.
| opportune wrote:
| I agree. It is honestly a fine language IMO and while I have
| nothing against Rust, I also haven't had much of an interest or
| incentive to use it. Yes, buffer overflow is a thing - it's
| never been an issue in my experience. Use after free and memory
| leaks are infrequent, easy to fix, and caught early.
|
| Golang is not a real replacement because 1. Golang core devs
| are too opinionated on random shit and make some things very
| hard to do without reinventing the wheel because "you shouldn't
| do that" partially because it's a corporate-owned language 2.
| GC. There are other minor things but those are the big ones,
| it's still an excellent backend language but can't replace C++.
|
| Besides Rust everything else is a toy without stability and
| backwards compatibility and/or lack of libraries. Rust is fine,
| it's just that the problems it tries to solve aren't something
| that experienced C++ devs often struggle with.
| Longhanks wrote:
| ...is not 2023.
| jdlyga wrote:
| There is a much safer and simpler language hiding inside of C++
| waiting to be discovered. If anyone can help reform the language,
| it's Herb Sutter with his cpp2 syntax. It's very much an
| experimental hero project, but it's promising:
| https://www.youtube.com/watch?v=ELeZAKCN4tY
| mdp2021 wrote:
| Sorry, I have only a sketchy idea of Zig: I understood it should
| have been a competitor? Why does the article writer in fact
| disregard it?
|
| The authors of Val state:
|
| > _Our goals overlap substantially with that of Rust and other
| commendable efforts, such as Zig or Vale_
| zozbot234 wrote:
| What's the difference between Val and Vale? Besides that one
| letter, of course. There's also Vala which is its own thing,
| but also in the "not quite C++" space. Very confusing!
| pjmlp wrote:
| Main one is that Val comes from previous Swift developers,
| and is kind of sponsored by Adobe.
| habibur wrote:
| This one is the best introduction. Never heard of Val
| before until these last few days.
| flohofwoe wrote:
| Zig is usually seen as a 'better C', not as a 'better C++'.
| Whether this is actually the case is debatable though (some of
| Zig's features are 'leaking' into the territory of higher level
| languages such as C++ or Rust).
| fckgnad wrote:
| I feel this viewpoint is pedantic. Zero cost abstractions is
| the space we're looking at and from this perspective, C and
| C++ are in the same space.
|
| If not then are we implying a successor to C++ needs to be
| Object Oriented? Because that's the main difference between
| C++ and C. Clearly the successor languages mentioned in the
| article do not have the same heavy OOP bias embedded into
| themselves as C++ and thus they all can be looked upon as
| successors to C as well.
|
| I think the goal here is just to find a language that does
| the whole "zero cost abstraction" thing better than the
| status quo.
| eptcyka wrote:
| I think some people believe C++ to be a mistake - if such
| people were to develop a new langauge they might take issue
| with framing the new language as a better mistake.
| ovao wrote:
| I don't know that I've heard anyone claim that C++
| _itself_ was a mistake, although there have obviously
| been many (acknowledged) mistakes made with C++.
|
| All things considered, for the time, given the paradigms
| we understood to be effective, and for the value within
| the design space, C++ was about as close to excellence as
| we could've hoped for. It didn't replace C, nor was it
| meant to, but for its purpose it was a great success.
| fckgnad wrote:
| No. C++ being a mistake is a valid argument. There are
| enough smart people claiming this that you can't dismiss
| it (Linus Torvalds for example). While the argument is
| not definitive it is valid enough that it must be
| considered.
|
| One thing I highly disagree with in your statement is
| this: " C++ was about as close to excellence as we
| could've hoped for."
|
| In my opinion, C++ is nowhere close to excellence. Best
| available option for certain applications is more inline
| with my opinion of it.
| ovao wrote:
| > One thing I highly disagree with in your statement is
| this: " C++ was about as close to excellence as we
| could've hoped for."
|
| It's fine to disagree, but it seems mildly disingenuous
| to omit all the qualifiers I (very deliberately and
| intentionally) prefaced that statement with when you do
| so. In that sense, you're actually disagreeing with an
| opinion I don't actually hold.
| fckgnad wrote:
| I omitted out of brevity. But ok, I'll put it in:
|
| >All things considered, for the time, given the paradigms
| we understood to be effective, and for the value within
| the design space, C++ was about as close to excellence as
| we could've hoped for. It didn't replace C, nor was it
| meant to, but for its purpose it was a great success.
|
| I disagree with this entire statement. It's not even
| close to excellence even given all the deliberate and
| intentional qualifiers you put around it.
|
| I believe that when I re-clarify my comment in this
| context it becomes clear that I am in totality
| disagreeing with your statement.
|
| I think C++ became popular due to luck in the same way
| javascript came to dominate the frontend. I believe there
| could've been a number of possible better alternative
| outcomes if luck would have been different.
| kllrnohj wrote:
| > There are enough smart people claiming this that you
| can't dismiss it (Linus Torvalds for example)
|
| Torvald's anti-C++ rant was full of factual errors when
| he wrote it and it hasn't improved with age.
|
| If you want to point to "smart people claiming C++ was a
| mistake" you need to actually use people who actually
| know & have experience with C++ in the first place -
| which isn't Torvalds.
|
| There's also other smart people, like John Carmack, who
| have been converted from C to C++ and now prefer the
| later especially for working in a team environment. C++
| is far from perfect, but there's also pretty strong
| arguments that it does still represent an improvement
| over C on average. Yet (almost) nobody argues that C was
| a mistake.
| fckgnad wrote:
| >There's also other smart people, like John Carmack, who
| have been converted from C to C++ and now prefer the
| later especially for working in a team environment. C++
| is far from perfect, but there's also pretty strong
| arguments that it does still represent an improvement
| over C on average. Yet (almost) nobody argues that C was
| a mistake.
|
| Agreed. Here is Wikipedia article on criticism of C++.
| https://en.wikipedia.org/wiki/Criticism_of_C%2B%2B
|
| The second sentence has a short list of some of the smart
| people who don't like it: "Among the critics have been:
| Robert Pike, Joshua Bloch, Linus Torvalds, Donald Knuth,
| Richard Stallman, and Ken Thompson". I'll leave it up to
| the reader to research why they criticize C++ (not C).
| However in the references there's a good article from Rob
| Pike:
|
| https://commandcenter.blogspot.com/2012/06/less-is-
| exponenti...
| flohofwoe wrote:
| I'd say, RAII and the template system are the main
| difference between C and C++, not the OOP features. "Zero
| cost abstraction" is mainly just a welcome side effect of
| the compiler's optimization passes, and those work in C
| just as well as in C++ (but typical C++ code depends more
| heavily on compiler optimizations to clean up redundant
| "left-overs" from template resolution).
| Koshkin wrote:
| > _RAII and the template system_
|
| Also function and operator overloading, often overlooked
| while being of critical importance.
| fckgnad wrote:
| No. Zero cost abstraction was a term used by inventor of
| C++. It was one of the design principles. Not a side
| effect.
|
| Source: https://www.stroustrup.com/ETAPS-corrected-
| draft.pdf
|
| Search for "Zero-overhead abstraction mechanisms".
|
| If you read it you will see Bjarnes goal was to provide
| the highest level of abstraction possible at "zero
| overhead". At the time he was creating the language, OOP
| was the newest and "highest" forms abstraction around...
| problems with it as an abstraction weren't as evident
| back then as they are now.
|
| But yeah, I agree that you can make an argument for RAII
| and templating being the main differences over OOP. But
| Zero cost abstractions are not a side effect at all.
| c-cube wrote:
| There are so many more differences between C and C++.
| Templates, RAII, type inference, lambdas, etc. These are
| the abstractions that try to be zero cost; C has little
| abstraction overall so it doesn't claim "zero cost
| abstraction".
|
| I don't think you need to be fully OOP to claim to be a C++
| successor, but you definitely need to have decent zero cost
| abstractions, including a form of templates/generics.
| Having RAII might also be a requirement.
| pjmlp wrote:
| All the successor languages mentioned on the article have
| ways to provide methods and some form of polymorphism
| directly supported in the language, and not the OOP in C
| kind of kludge.
| fckgnad wrote:
| polymorphism is not an OOP concept.
|
| https://en.wikipedia.org/wiki/Polymorphism_(computer_scie
| nce...
|
| You will see Wikipedia doesn't treat it as such. Examples
| are shown in functional languages and it is not
| hierarchically categorized as a exclusively a part of
| OOP.
| pjmlp wrote:
| From that link,
|
| "In a 1985 paper, Peter Wegner and Luca Cardelli
| introduced the term inclusion polymorphism to model
| subtypes and inheritance,[2] citing Simula as the first
| programming language to implement it"
|
| Polymorphism is part of what makes a language OOP
| capable, naturally it also exists in other paradigms.
|
| We can also do other examples like discussing the
| semantics of closure and methods equivalency.
|
| https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent
|
| Do you also want Simon Peyton Jones point of view on
| Haskell type classes and OOP relate to each other?
|
| "Classes, Jim, but not as we know them"
|
| https://www.microsoft.com/en-
| us/research/publication/classes...
| fckgnad wrote:
| We can keep the discussion going but I don't see why?
|
| Polymorphism is not part of OOP it's much more universal
| then that. It can be used by OOP the same way numbers can
| be used as in OOP programming. But numbers are not
| hierarchically under the umbrella of OOP.
|
| I mean what is the point you're trying to make here? That
| polymorphism is fundamentally and categorically OOP? Are
| you saying that anything that uses polymorphism (which is
| almost every language under the sun) is OOP?
|
| >Do you also want Simon Peyton Jones point of view on
| Haskell type classes and OOP relate to each other?
|
| No? Wouldn't that be a completely different topic?
| pjmlp wrote:
| All worthy candidates to successor languages to C++ also
| support OOP in some form, that is the point.
|
| Not the OOP from _all I know is Java_ , rather the OOP
| from SIGPLAN, ECOOP, IEEE, ACM papers, which happen to
| have polymorphism as one of the common features across
| all variations of what can be considered as an OOP
| capable language.
| fckgnad wrote:
| The 'Mutable value semantics' section reads like a carbon copy
| of the Rust borrow checker. Is it just me or is val just
| ripping it off completely? I'm honestly not sure of this part
| so if I'm wrong someone can let me know.
|
| Other than that, the whole title of the article is
| manipulative. It's not a fair and balanced examination of all
| the possible successors of C++. The author admits he works on
| Val, and the article is obviously promoting Val as superior to
| other alternatives.
| zer0zzz wrote:
| Very little discussion of swift and how the current work on C++
| Interop is in many ways a peak into what carbon and others may be
| in several years.
| awesomegoat_com wrote:
| And the next year will be the year of THE
| Successor Language
|
| And that's (of-course, hands down) zig. Thank me later. :-) Jokes
| aside, I really like where zig is headed. As a gambler, I am
| willing to bet on zig over val any time and twice on Mondays.
| FpUser wrote:
| Zig is really nice language with some good concepts. However it
| is not a successor to C++.
| transfire wrote:
| Unless there is significant reason to avoid garbage collection
| (for the vast majority of software there isn't) then Crystal is
| the best C++ replacement out there. Its Ruby syntax is easy to
| read and write, its OOP model derives from Smalltalk (via Ruby).
| And it has easy interop with C++ code.
| suby wrote:
| Crystal is interesting, but lack of incremental compilation
| seems like it forces a fundamental limit on how large the
| projects you're working on should be. Am I wrong here?
| anonymoushn wrote:
| Aren't users who are okay with GC already not using C++?
| p0nce wrote:
| A lot of C++ software out there could afford to use a GC.
| wiseowise wrote:
| What's wrong with RAII?
| pebal wrote:
| It is not a solution for all cases.
| wiseowise wrote:
| Care to give cases where it is not a solution?
| hgs3 wrote:
| RAII amounts to reference counting which means you need
| to handle circular references yourself. A garbage
| collector should handle them automatically. In addition
| to memory, GC's can catch other unused resources like
| unclosed file handles.
| fasterik wrote:
| Arena allocation is useful when many objects have the
| same known lifetime. An example from games is per-frame
| allocations. All objects needed only for the frame being
| rendered are carved out of a large chunk of preallocated
| memory. The chunk is reused every frame instead of being
| freed back to the system allocator. In addition to the
| performance benefits, this results in less memory
| fragmentation.
| pebal wrote:
| - Some graph structures. The shared_ptr does not release
| circular data structures and has a lot of memory
| overhead.
|
| - Memory managing of concurrent containers. Lock-free
| algorithms requires GC or the hazard pointers and these
| are basically based on GC.
|
| - You often need the GC engine when you implementing or
| integrating another language.
| UncleMeat wrote:
| Unless you use shared_ptr for all heap allocations (and
| are also careful about references to stack-allocated
| data) it allows for uaf bugs. And at that point you are
| probably better off with a GC since you've got all of the
| problems with a reference-counting GC but none of the
| opportunities for optimization.
| FpUser wrote:
| Modern C++ provides enough nuts and bolts to avoid
| explicit allocations. My current C++ project (backend
| business server) only has single one. The rest is handled
| happily by RAII (what a dumb name). I could eliminate
| that single explicit allocation as well but I think I
| could manage single occurrence ;).
|
| Also It would be really dumb to use shared_ptr for every
| case of allocation
| UncleMeat wrote:
| "I only every allocate things on the stack" is not
| sufficient to have safe lifetime access. I've seen enough
| cases of some stack-allocated string getting passed as a
| string_view and then blowing up because somebody stored
| it.
|
| "I have a complete tree of object ownership with a single
| allocated root" is also a mess for a different reason -
| you literally never delete anything. Either you have a
| very very strange application or you are being massively
| wasteful with memory pressure.
|
| It _would_ be really dumb to use shared_ptr for every
| allocation. But it is the only way in C++ to
| systematically ensure that you never have a uaf (I guess
| you could also have a custom allocator and a custom
| pointer type that does a null check on every dereference
| - but now you are paying a major performance cost for a
| lock and a branch on every dereference). That 's why I
| mention the benefit of a GC. You can get safe lifetime
| access without shared_ptr everywhere (or going all the
| way to where Rust went and demanding very explicit
| lifetime annotations for the compiler to use).
| FpUser wrote:
| >"I've seen enough cases of some stack-allocated string
| getting passed as a string_view and then blowing up
| because somebody stored it ..."
|
| Shortest answer - I do not get scared into using
| particular tools just because they're "safe" and in
| practice I've never had to deal with the problems you
| mentioned in my products. There are also various tools
| that catch these kinds of errors for C++ and other
| languages. I do use those them tools ;)
| UncleMeat wrote:
| A major part of my career has been to _write_ these
| tools. There 's a reason why none of them are sound. I do
| not believe that modern C++ and the surrounding tool
| ecosystem is remotely capable of preventing UAF bugs in
| complex applications, which is a big problem for many
| applications given the potential consequences of
| incorrect memory access.
|
| You might belong to the rarified class of uber-developers
| who can write bug-free C++ applications, but if that
| class of people exists it is a small one.
| moloch-hai wrote:
| shared_ptr use is very rare in good modern C++. Uaf bugs
| are much rarer. Most people using modern C++ never have
| any, see any, or risk any. So, no, they would not be
| better off with GC.
| UncleMeat wrote:
| > shared_ptr use is very rare in good modern C++
|
| That's true. But if you do the preferred thing of using
| unique_ptr then you still happily access memory beyond
| the lifetime of an object via some bug.
|
| > Uaf bugs are much rarer.
|
| This is not true. You can go look at CVEs for large
| projects like Chrome that have teams of people trying to
| weed out these kinds of issues and still see UAFs where
| absolutely nothing is allocated with "new."
| moloch-hai wrote:
| That's Google for you.
|
| You cannot access memory you have no pointer to, not even
| accidentally.
| Rochus wrote:
| nothing
| p0nce wrote:
| Basically if your C++ function takes a pointer you need
| to write one for unique_ptr, or raw pointer (documenting
| the ownership).
|
| With a GC, there is a global owner so you don't have to
| think about ownership for everything, this breaks down
| for some resource release (maybe half of objects) which
| is not necessarily easier. But typically GC languages
| also allow you to use RAII, though maybe not as
| consistently as C++.
|
| What kind of programs can have unclear ownership?
| Basically everything that doesn't need to be high
| quality.
| Yoric wrote:
| I can think of a few cases in which GC would really
| simplify things, both in C++ and in Rust. I want to do
| some serious D and Go (and Crystal?) some day to see how
| it changes life.
| Koshkin wrote:
| Unlike GC, RAII is "eager" - which has performance
| implications.
| de_keyboard wrote:
| RAII can push resources to a collection that is disposed
| later
| rurban wrote:
| I miss pony, rune, nim, crystal and many more. rune has an
| explicit 'replace C++ in prod' goal.
| auxym wrote:
| Indeed. And Nim has very good FFI with C++, including templates
| even.
| chubot wrote:
| Hm I never heard of Rune, and I have heard of all the others
| (although this article gave me more info about Val):
|
| https://github.com/google/rune (side project, not Google
| project)
|
| This part is interesting, as I've found many benefits from
| having a layer of indirection between the app-level data
| definitions, and the C/C++ struct level. I recall that Jai used
| to advertise the SoA -> AoS transforms but that was many years
| ago.
|
| _It provides many of its features by deeply integrating the
| "DataDraw" tool into the primitives and constructs of the
| language. DataDraw is a code-generation tool that generates
| highly-optimized C code which outperforms e.g., the C++ STL
| given a declarative description of data-structures and
| relationships between them. For more information, see the
| DataDraw 3.0 Manual._
|
| I've never heard of DataDraw either .. I wonder who uses it?
| gavinray wrote:
| I can field questions folks might have on DataDraw. I'm not
| Bill, but I wrote the docs PR that recently overhauled the
| Rune README to highlight a lot of this interesting info about
| its use of the DD tool.
|
| Another neat thing about DD -- the Rune compiler/grammar
| itself are written as DataDraw types. All user-defined
| classes/types are compiled under the hood into DD types.
|
| One of the builtin things you can do is generate PostScript
| visualizations of them. Check this out:
|
| https://github.com/google/rune/pull/33#issuecomment-13558283.
| ..
| azhenley wrote:
| I really enjoyed reading about Austral, though it is still really
| early, that was discussed on HN this week:
| https://news.ycombinator.com/item?id=34168452
| Taikonerd wrote:
| Agreed, Austral looks really cool, and the author's explanation
| of "what a linear type is and why we use them" was the clearest
| I've ever heard.
|
| I was surprised it uses an Ada-like syntax -- I thought the
| standard wisdom for new languages was to either imitate C or
| Python ;-)
| zozbot234 wrote:
| Article fails to mention that autocxx and crubit are aiming for
| much improved interop between Rust and C++. Sure they're
| experimental, but not more so than Val.
|
| Rust devs are also pushing for making more of its core and std
| library "unsafe" friendly, to reduce the risk of unsound calls to
| Safe Rust from an unsafe environment with possible aliasing,
| pinned-in-place objects, self-references, uninitialized ("write-
| only") memory etc. etc. So that in itself will also deliver
| improved semantics between Rust and C++.
| Ambroisie wrote:
| Can you explain what you mean, about unsafe friendliness in the
| standard library?
| ysleepy wrote:
| I believe this is referring to the interop abi that is in
| planning.
|
| It aims to expose a nicer way to glue rust idioms to similar
| or equivalent idioms in other languages.
|
| https://github.com/rust-lang/rust/pull/105586
| SirGiggles wrote:
| Not the poster of the comment but I'd like to hear from the
| downvoter why this was downvoted.
|
| As a Rust novice, interpreting the comment GP made (from
| the perspective of, for example, C++ calling Safe Rust core
| or stdlib) this seems like a valid response.
| SirGiggles wrote:
| Would the downvoter like to comment as to why I too got
| downvoted?
| c-cube wrote:
| It's pretty strange. Rust is dismissed because interop with C++
| is hard, but then Val is cheered on despite its C++ interop
| basically being a big "todo" item.
| epolanski wrote:
| Author admits his own bias and preference at the end.
| ModernMech wrote:
| > I do need to confess: in my spare time, I have started
| working with the Val team
|
| Indeed, I think this should have been made clear in the
| first section. I thought when the author said they have a
| bias it was more in the general sense, not that they are
| working on one of the languages in question.
| MoscMob wrote:
| Yes, misleading.
| kibwen wrote:
| Tip for blog authors: if you open your posts with apparently-
| sincere references to TIOBE, readers will immediately close the
| tab and conclude that your content is not worth reading. TIOBE is
| a joke, and has been for decades.
| eps wrote:
| https://web.archive.org/web/20230102100639/https://accu.org/...
|
| Val: https://www.val-lang.dev/
|
| Carbon: https://github.com/carbon-language/carbon-lang
|
| CppFront: https://github.com/hsutter/cppfront
| linuxftw wrote:
| Great talk on CppFront given recently at cppcon:
| https://www.youtube.com/watch?v=ELeZAKCN4tY
| tialaramex wrote:
| That Val site provides the following example, which makes no
| sense to me: public fun main() { let
| weight = 1.0 print(weight) // 1.0 let length =
| 2.0 print(length) // 1.0 }
|
| Is the behaviour of print "Just print whatever the first
| variable was forever" ? Is this example wrong ?
___________________________________________________________________
(page generated 2023-01-02 23:01 UTC)