Hacker News new | ask | show | jobs
by jmull 963 days ago
I'm heartened by the success of the Typescript model of improving things when there's a deficiency/problem.

The alternate approach (which is extremely popular unfortunately), is to throw it all out and rebuild everything from scratch. I guess it's fun and exciting, which attracts developers, but it takes a long time to achieve any level of maturity and are hard to sustain (the people who are in it for the fun and excitement will move on before too long).

4 comments

The only problem is you run the risk of implementing a still developing standard that might be vastly different than the finalize version. You then have code and programmers trained in doing something the old "wrong" way.

This happened in TypeScript when they added support for an early version of decorators and now the TC39 version (which is still only Stage 3) is just different enough to cause issues.

They have been labeled experimental the entire time and off by default. That specific TC39 proposal is also approaching a decade of feet dragging and has been exceptionally slow to make progress. In Typescript if you aren’t opting into using decorators through a library that forces you to use them, they’re entirely avoidable (and some would argue, decorators make code worse and this failure to launch is a good thing).

I think the Typescript creators themselves learned a lesson with decorators and enums which is why we haven’t seen other JS language proposals get added until they’re actually in the process of being adopted (e.g. matchers).

> That specific TC39 proposal is also approaching a decade of feet dragging and has been exceptionally slow to make progress.

So... like the pipeline operator? Pretty much given up on that being included now. They can't make up their minds over two competing syntaxes (and, FWIU, it has taken them years to decide).

Why did you mention enum? Is it designed badly?
I think the main complaints are:

1. Enums are one of the few TypeScript features that isn't a type annotation that can simply be erased. Enums emit code and don't have an equivalent JS feature.

2. Const enums are unsupported by some bundlers/build tools, and so people try to use them and then got burned at build time.

3. The use cases covered by enums are often better served by union types.

None of the above is necessarily fatal for the feature. Certainly people used to enums in other languages still like them. But all 3 combined and the general recommendation these days ends up being just don't use them.

FWIW, Babel has supported const enums for a few years now (since 7.15)

https://babeljs.io/blog/2021/07/26/7.15.0

Yeah, tools can add support for them, but they're fundamentally a whole-program-optimization in a build chain that's 99.9% file-by-file.

Supporting const enums will, by necessity, greatly reduce the maximum performance that toolchain can achieve, because you have to evaluate the entire project to tell whether `Foo.BAR` should be left alone or replaced with a constant defined elsewhere. And in the worst case of an ambient declaration, "elsewhere" could be any file in the project.

You mean the feature that was gated behind an opt-in switch called `experimentalDecorators' and documented from the start with "Experimental support for decorators is a feature that is subject to change in a future release"?

This was only really added to appease Google and Angular 2.0.

Yeah Microsoft didn’t want AtScript to steal the little thunder TypeScript had at the time, but we’re still left with that one experimental feature all over Angular codebases.
People who chase "new, shiny" often get rewarded with stupid prizes.
The TS approach is really dumb for C++ and completely unnecessary. Compile times are long enough as it is without another meta compiler on top.

You already have a compiler. Just make it emit binary-compatible code for the new dialect. You have modules now so you don't have the problem of supporting mixed-dialect headers.

The TS approach isn't about having a meta compiler. cppfront is just a temporary stepping stone, just like cfront (the original C++ compiler) was a temporary frontend for C++. Eventually the goal would be to add support for the new syntax to GCC, Clang, MSVC
Not just that, but a big part of Herbs feature set are things that can work as proof of concepts and proof of usefulness that could be presented to the C++ committee as actual features that can be added to C++. Many of the features of cppfront have already been proposed to committee.
This was dead when I saw it, which is weird because it's factually true. So I vouched for it.

Herb Sutter proposes a bunch of stuff to WG21 (the C++ committee) and most of it goes nowhere. We're not talking about two proposals here, maybe a dozen is closer. Most of that stuff from many years is in Cpp2 because it's Herb's language so they can't stop him.

The one thing Herb proposed in that time which got into C++ was the spaceship operator <=> which is basically like Rust's PartialOrd trait, you write one operator for your type, and for any pair of values (this & that) you decide whether this was Greater, Equal or Less than that -- or none of the above. As a result the compiler can implement all the obvious comparison operators like <= or == or > using that one piece of code you wrote, and it's easily able to be consistent. This is very nice, and it's in C++ today, but it's also in Cpp2 of course.

GCC and Clang are open source, though, and Clang has a sane codebase, so why not implement it in an experimental fork of Clang?
I think you're underestimating the amount of work it takes to maintain a fork vs a separate transpiler layer.

Why waste time on forking clang when you can both hash out your language design and have a highly functional prototype right away?

The people who are in for the fun won't use the typescript model for long either.

If it's optional it will be omitted in the hard parts, for instance if your favorite library doesn't support it.

It's still worthwhile even if you have to fudge module boundaries or skip the hard parts. Kind of like having "safe" Rust wrapping "unsafe" C boundaries doesn't make the rest of Rust not worth it. Lying about the type internals of your function is still preferable to the wild-wild west of JS.
That's why incremental adoptability is so powerful. Slightly ironically that's exactly what (AFAIK) made C++ so popular: You can start using it without really giving up anything. And ideally once you see the benefits you'll be hooked and want to (incrementally) use it more and more throughout your code.
And exactly what makes C++ codebases so hard to clean up from C idioms, as many developers to this day apparently never went beyond changing the file extension, regardless of how many security advocacy we keep telling them.
C++ adds so many additional security footguns over C, that I find this line of reasoning hard to accept. The problem with C++ is not that people are using C constructs with it, the problem is that the language design itself is deficient.

Are you aware of any systematic review that shows evidence that C++ is safer than C?

The rate of safety defects between major C and C++ projects appears similar at first glance, and both way worse than managed languages or rust.

I have written a lot of the same kinds of data infrastructure software in both C and C++ and other languages, so comparison is somewhat reasonable (unlike comparing e.g. web browsers and systems software). The rate of defects is much lower in modern C++ versus C, and the types of bugs have changed too, but only part of that can be attributed to safer constructs in C++.

C requires many times more lines of code than C++ to do the same thing. AFAIK there is considerable academic evidence that bug counts roughly scale with lines of code, so languages that are precise and concise naturally reduce total defect rates. Minimizing defects requires maximizing expressiveness. The ratio of LoC between languages to express the same thing is not constant, it depends on the application.

The kinds of bugs I see in C++20, given the type of software I work on, are almost entirely the same kinds of logic and behavioral bugs that occur in every language. This is why Rust isn't as popular as one might expect for systems software: memory safety bugs are not a thing for many code bases, and Rust requires many more lines of code compared to C++20. I am sure Rust will become more economical over time but for now it is pretty verbose and has pretty limited metaprogramming functionality.

C++20 is remarkably safe and concise if you take full advantage of the type system.

> C++20 is remarkably safe and concise if you take full advantage of the type system.

Against what sort of laughably low bar are you measuring to make C++ "remarkably" safe ?

This is a language which delights in deliberately adding more footguns, on the rationale that well, it's less safe so surely it'll be faster right? No need to measure, no need to investigate what actual performance optimisations might somehow be available if we allowed the dangerous behaviour, no, just mandate it and YOLO.

C++ 20 introduces std::span. Now, std::span is basically a slice type, Rust's [T], and to some extent it's remarkable that C++ didn't have a slice type, but that's C++ for you. What's fascinating is that in 2020 I remind you they standardized a type which deliberately has no safe way to use it. It was proposed as a safe type, and WG21 stripped out the safety on the rationale that now it's faster (see above) then rejected all attempts to add the usual half-arsed C++ safety features to the type now that it wasn't safe by default.

Let me quote a C++ proposal paper (this isn't some hit piece from Rust fanatics, it's a serious proposal to the ISO working group) P2821 on std::span:

"Ultimately, this becomes a stereotypical example of how C++ traditionally handles safety. this example gets to be pointed at for years/decades to come. All of this could have been avoided"

Yes, it starts by not coding in C++ as if it was C.

Use templates instead of macros, RAAI instead of gotos, namespaces instead of prefixes, bounded checked strings and arrays instead of raw pointers, new instead of error prone sizeof with malloc(),...

> The rate of safety defects between major C and C++ projects appears similar at first glance

How come? Surely there would be fewer memory leaks in a code base with proper RAII than in C code with malloc and free all over the place.

Yes, this is a common theory, but I don’t see evidence for it in the hard numbers. Taking two of the most popular projects in each language, with a comparable LOC count, the numbers look surprisingly similar year over year:

Linux kernel (C): https://www.cvedetails.com/product/47/Linux-Linux-Kernel.htm...

Chrome (C++): https://www.cvedetails.com/product/15031/Google-Chrome.html?...

There’s some variability year over year, but if anything C appears to have a slight advantage over C++ in terms of memory corruption (840 vs 1004), with essentially the same number of overflow errors (322 vs 328). There is no comparable rust project, but initial evidence from the asahi gpu drivers hints that memory corruption errors are fundamentally eliminated.

This is obviously not accounting for confounding factors, hence my request for any peer reviewed evidence for the security claim. Until then, the facts don’t seem to be supporting it.

You're assuming that all, or even most, C++ codebases use proper RAII and don't run wild with the vast amount of features in the language and standard library.
C++ containers such as std::vector and std::array have bounds checking at least, though I have not saw them used very much.

Bounds checking instantly eliminates buffer overflow related unsafety, but not having it as the default is not good.

The trouble with bounds checking via generics/macros is that the compiler doesn't know how to optimize out the checks. Most bounds checks can be optimized out of inner loops, where it really matters. But if the overflow test is just ordinary code, the compiler can't do that.

You also want to hoist bounds checks and do them early. Often, one check at loop entry can eliminate the bounds checks for each iteration. But the language has to allow an early fail.

They have bound checking through the `vec.at(index)` method, not through the indexing operator `vec[index]`. Most people won't even look at the `at` method, they will just use the indexing operator since that's supposed to be the "default" .
But you can't deny that C++ is a straight upgrade to C. If CppFront becomes this straight upgrade to C++, isn't that a good thing?
C++ extended C syntax, did not introduce a completly different one alongside.
Does that make it not an improvement? There's definitely issues with C++'s syntax...
The alternate approach is also popular because there is no success in the typescript model, as in critical deficiencies get unfixed for decades (with some of the same "not fun" challenges )
There is no success in the Typescript model...? What on earth are you needing that off of?
what is your list of successes besides TS?
Ironically... C++.

C++ was itself the TypeScript model applied to C.

And, in fact, many of the things people hate about C++ are the result of exactly the kind of compromises you have to make to apply a TypeScript-like model to language evolution. C backwards compatibility is what made C++ successful originally but is now one of the main things that makes C++ crappy today.

What hasn't gotten fixed in typescript for "decades"?
The 10x complexity and 50x unsafety