The first new build of Circle, a new C++20 compiler, since April 2022 is online
github.com
github.com
It's clear why Carbon looks the way it does - they want to do what Kotlin did for Java and using the same strategy. But, it won't work. They understood Kotlin's strategy as binary compatibility at the per source file level but that's only half the story. The quiet monster of Kotlin's success is j2k which is a source translation tool built into the IDE. It rewrites a Java file to Kotlin and then applies all the intelligence in the ide plug-in to clean up the results, meaning even idioms get translated properly.
Jetbrains could do that because they were developing the IDE plug-in alongside the compiler from day one, and because Java is quite easy to parse, and because Kotlin deliberately constrained itself to Java semantics even when sub optimal.
I had high hopes for Carbon when I first heard about it but that number of proposals without even having a compiler is crazy. How do you port existing code? You can only use it for newly added code, presumably, and only if your new code doesn't interact with any c++ using the bits the carbon team don't like? That's light years from the type of interop that made Kotlin a success.
Sounds like this guy actually understands the problem in depth. I hope he's able to attract customers, though it's a tough sell to make your company codebase depends on a language maintained by only one guy.
Carbon and C++Next both seem very directly "inspired" by Circle, but neither of those efforts seem to have actually produced anything yet.
Ideally, Circle it would be open source. But I understand wanting to hold out for some amount of corporate sponsorship first.
constexpr is in C++11, Jai is from 2014, and Zig 0.1.1 is from 2017.
This project looks quite cool though.
constexpr in C++ 11 is indeed very limited, arguably even more than Rust's const functions today.
Isn't it done on top of LLVM/Clang++?
The front-end compiler and standard library are written from scratch, but it uses LLVM as the backend. The standard library is not yet complete, but it's amazing how it uses Circle's unique features to implement in a very elegant and efficient manner certain classes like variant and tuple which are absolute monstrosities of complexity in clang/g++/MSVC.
cpp2 is currently my favorite contender in this arena
Everything else requires rewriting everything, and they can only support a limited subset of C++ features, so if the goal is full compatibility with existing code they aren't going to achieve it anyway.
For full rewrites, we already have enough alternatives with more maturity years behind them.
Until the likes of LLVM, GCC, CUDA, V8 and co get rewritten into something else, improving existing C++ codebases is still an issue.
It's the same strategy here. Carbon is (was?) also intended to compile to the C++ ABI and so does Circle, that's why it says it doesn't run on Windows (commercially a huge error. many of the biggest C++ codebases that could benefit are running on Windows like game engines, and Windows doesn't impose a specific C++ ABI anyway). But there's no way to take existing code files and switch them to the new language in Carbon, at least not yet. Kotlin was carefully designed at every step of the way such that every Java program can be expressed in Kotlin without change, even if that meant compromising on some things. Seems like the Carbon guys are being sucked up by the lure of safetyism and just want to make their own version of Rust that happens to compile to the Itanium C++ ABI.
Loom, value types, SIMD, Panama,... are all features that start to be an issue to expose as Kotlin features.
It is working on Android, because Google is pushing it as Java 8 => Kotlin, not as Java XYZ <=> Kotlin.
SIMD/Panama stuff is just new APIs. No problem there. Kotlin maybe even wins because Panama is super verbose.
JVM value types seems never to arrive. Stuck in dev hell for years maybe. If/when it ever does see the light of day, Kotlin has value types and so it can just be compiled to a JVM value type to get the benefits. You'd need to opt in to an ABI break but no big deal.
So the binary compatibility part hardly seems an issue even looking forward a long way into the future.
It's probably harder for C++ because the language there seems to evolve quicker than Java!
KMP taken to the extreme, is better for Kotlin just to do its own thing with Kotlin/Native, except it is so behind that JetBrains is using Rust instead for Fleet services.
Java is expected to have language level support for some of those features, will Kotlin catch up?
However, before reaching that point, piecewise refactoring is something of considerable value. There are huge C++ codebases lying around that I believe would benefit immensely from a rewrite in a safer language, even if it's not as safe as Rust (or Scala, or F#, etc.)
Firefox and Chromium have already paid the entry price for piecewise refactoring into Rust, so they're probably not the best candidates. But there are many other examples that undoubtedly are, e.g. LLVM.
Now, Scala is a JVM language and so presumably you get Java's rules, you aren't Sequentially Consistent but the damage is constrained - however human programmers still can't successfully reason about this state in non-trivial programs.
Do you have any evidence to back up your claim?
Both the JVM (Scala's VM) and the CLR (F#'s VM) have a history of vulnerabilities:
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=java+hotspo...
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=dotnet+clr
Programming languages running on VM is not a safety guarantee.
The semantics of F#/Scala are memory safe because they're defined to be dynamic. The semantics of Rust are not memory safe. A subset of Rust is provably memory safe, but none that uses eg. much of the standard library which is unprovably memory safe against the lang's own semantics.
But, at the outset, we can simply observe that the "safety" Rust provides is memory safety, which is a non-issue in a dynamic language.
To say that Rust is as safe, or more safe, than a dynamic lang is false. It's strictly less safe, since by construction, it is a language where allocation is managed by the programmer.
The whole argument about "saftey" in the sense Rust addresses is between languages of "manual allocation". To imagine that any of these language is "safer", is to have misunderstood a great deal. Rust is about enabling an otherwise C-programmer to limit the impact of manual memory handling, it is *not* to prevent eg,. use-after-free errors in javascript. Since there are no such errors by construction.
But, Safe Rust also chooses to construct safety that those GC'd languages don't have.
The reason Javascript doesn't have data races for example is that it doesn't have concurrency - can't have a race with only one runner. Java does have data races (with limited but still unsettling consequences), Go's data races are really bad, like actual Undefined Behaviour bad in some cases. Whereas Safe Rust doesn't have data races - you can write concurrent Safe Rust but you can't write a data race.
The difference you're grappling for probably feels intuitively like it should exist, but I assure you it does not.
I will resolve this, simply, by saying whatever advertisement Rust makes for itself, is better made by any "dynamic" language.
And if we're inclined to trade PR against PR, Rust looses. Managed memory has better PR.
And so those repeating the Rust agitprop against managed languages are simply "NPCs for the wrong ideology".
Theoretically, memory-safety between Rust and managed languages is equivalent.
However, memory-safe languages (i.e. not Managed C++) running on a memory-safe VM have one more layer of defense in depth than Rust. This matters in case there is a bug in the compiler or any of the dependencies. Additional layers may include, for instance, Unix and containers.
"dynamic" languages, though? That's entirely orthogonal to memory-safety and not very good for general software safety.
Of course:
- this doesn't cross library boundaries;
- this assumes robust enough static analysis, which is not possible in the general case, since templates are non-decidable in the general case – so you need to restrict your new language to more reasonable semantics;
- let's not even get started on the interaction between templates and C-style macros – at some point, you need to throw the towel.
Successful piecemeal rewrites will result in gradual improvement, so if you don't see that gradual improvement over time, something is very wrong with the project.
I'm more interested in Val than the rest, but, Val isn't really a C++ replacement (in terms of syntax, at least), it's a language that offers C++ interop. To quote an old joke 'How do you get to Dublin? Well, firstly, I wouldn't start here'. I'm not sure whether any new language in 2023 should start with C++ interop as one of its main goals.
Since when? Last time I looked C++ had almost a hundred keywords. C++ 20 added a bunch including "requires" and "concept" - both ordinary English words which oops, too bad now your software is incompatible because it used the wrong identifier.
do not want to go through the whole article but have this question:
fn ParseAsInt()
The goal for this new languages like Rust, Carbon, Go etc. is to introduce "better" alternatives to C and C++ languages and move the developers over. Nothing is wrong with that. Now to syntax: I completely understand syntax constructs that actually add value and deal with new / changed concepts like memory management, concurrency safety etc. However what is the point of changing already established syntax just for the fuck of it. What is fucking wrong with int X(). Why change something that does not need changing and increase the impedance of getting into new language?
And the declaration syntax for C++ is so bad that, in the most general case, you can't even reliably tell if it's a declaration or not without knowing the compiler that'll process it! Consider:
template<size_t N = sizeof(void*)> struct a {
template<int> struct b {};
};
template<> struct a<sizeof(int)> {
enum { b };
};
enum { c, d };
struct test : a<> {
friend int main() {
b<c>d; // declaration or expression?
&d; // error or ok?
}
};
The first commented line above parses either as: b<c> d; // local variable
or as: ((b < c) > d); // expression
depending on whether sizeof(int)==sizeof(void*) on that particular implementation. Consequently, the following &d is either legal or not, depending on whether it's trying to take the address of a local or of an enum constant.That alone is, to me, sufficient reason to believe that changing it to Pascal-style "name: type" declarations, which are unambiguous, benefits both tools and humans, even if it's slightly more verbose.
I am not against it. It is a different language altogether though. We were talking about "fixing" C++ while it is still being C++.
I'm basically looking to evolve on multiple fronts at once. If there's an interest in new syntax, put resources. If there's interest in a borrow checker (I'm sure there is), put resources there. Just move up the field however you can.
Seeing function overloading show up surprises me. The only thing I can think of is having different semantics between different overloads, but removing overloads doesn't remove the issue. You just now have a poorly named function that isn't an overload.
I think the contrast between string contains in Rust and C++ is illustrative. Overloading means you only get whatever parameter types the stdlib provides in C++.
Overloading is a don't-repeat-yourself thing - if you have a bunch of functions that do fundamentally the same thing with different types, encoding those types in the function name when they're already explicit in the arguments is simply redundant.
Then there's an issue with ABI stability. Adding a new function argument breaks any existing compiled code even if there is a default value, but adding a new overload and redirecting the old one to call the new one with the defaults is fine.
That's where you should use parametric polymorphism, that's my point.
C++ 23 defines _3_ overloads of string.contains() with parameter defined as variously a char, a char * and a string view, enabling name.contains("Jim") name.contains("Steve"sv) and name.contains('Q'). But if you need a fourth, too bad.
Rust doesn't have overloading, so it defines string.contains() as polymorphic over the parameter type Pattern, which enables name.contains("Jim") name.contains('J') name.contains(char::is_uppercase) name.contains(['B', 'o', 'b']) name.contains(|c| { my_fun(c, 206, local_var) }) and so on and so forth.
One reason C++ doesn't do this is addressed in Circle, by offering "interfaces" which are approximately equivalent to Rust's traits or the C++ 0x Concepts (as Sean firmly points out, these were very different than Stroustrup's C++ 20 Concepts)
The problem is, what does 'Q' have in common with "Jim" ? In conventional C++ polymorphism we want a base class and of course these are basic types, they don't have a base class, much less one which has the necessary commonality.
With interfaces, we can say what we care about isn't some hypothetical "base class" of string_view and char, but instead a common interface they both have dedicated to string matching, and now we're writing polymorphic software again.
In C++ I need to overload doop() exactly three times, once for each of the three types we identified. I can't do it any other way, that's how it must be defined.
But in Rust I just write it once, in terms of Pattern, I don't need to understand how Pattern is actually implemented, that's opaque to me, I just use this Pattern trait and it all works.
edit: to elaborate: some functionality can be implemented generically (i.e. parametrically ) in term of some other concepts, recursively. At some point the concepts need to map to actual concrete implementation, then you use ad hoc polymorphism. This is the same in rust and in c++ [1].
Additionally in C++, even when you can implement some functionality parametrically, it is sometime useful to (partially) specialize functions and types to take advantage of some optimizations (for example it is possible to implement std::copy generically, but it is often specialized for contiguous iterators to trivially copyable types to call memcpy).
[1] Rust does of course have a more principled handling of these concepts, which are often underdefined in c++.
Do you have an example I can look at where it's done the way you imagine it "should" be done in C++?
The reason the standard specifies the three overloads of course is because chars and chars literals that have been inherited from C are funny and those begin/end pairs are dangerous. But that's a quirk of C++ string types and nothing to do with parametric and ad-hoc polymorphism in C++.
edit: also the three overloads in MSVC probably forward to the same implementation over string_view (except possibly for char of course that can be implemented more efficiently).
edit2: I use concepts in my implementation just because. It would work just fine without them.
I believe that your hypothetical approach, ignoring the fact it's not practical, is parametric polymorphism. We can invent more types of "needle" and use the same "needle" concept for other predicates, thus if we have N needles and M predicates that's N+M work, not N*M work as with the overloads.
However, what is actually practical, and thus what is done, is just ad hoc polymorphism via the overloading we looked at, if WG21 wants to expand it that's N*M implementation cost.
I do not believe we can claim something is ad hoc polymorphism solely because somewhere there's different implementation code for type A and type B as with your Godbolt example, if you believe that's ad hoc polymorphism surely you end up thinking std::sort is also ad hoc polymorphism which seems like nonsense.
My "hypothetical" approach is bog-standard generic programming in c++ has it has been practiced for almost 30 years since Stepanov originally codified it.
ADL was litterally designed to make this sort of stuff work.
You're right that in some places C++ does the generic thing, I pointed at std::sort already - there are lots of examples of varying quality. The way we got here is that I pointed out overloads are the Wrong Thing™ and that's why it makes sense Carbon would want to outlaw them, and why no, outlawing them does not lose the nice property where the API feels generic - you can do parametric polymorphism and still have the generic API.
The main thing outlawing overloading gets rid of is actually functions/ methods which quietly have more than one distinct purpose. These functions are foot guns. Use your words, call the two (or more) things by different names reflecting the difference in intent and then fewer developers will mistakenly invoke the wrong meaning.
I don't like ADL either, but it's pretty far off topic.
Can't comment on Conan but, I hear it's nice