I’m not suggesting you jump off the C++ cliff. Don’t use the STL. Don’t use RAII. Don’t follow “everything has value semantics and a = b does massive allocations and copies”. Don’t use the STL. Don’t use smart pointers. Don’t use exceptions - you can disable them at compile time. Maybe use the STL if you can’t be bothered to implement things yourself, but measure your build times before and after.
Start with C, and introduce C++ features when they let you write the same C code but more concisely/safely. Use operator overloading for mathsy types. Add an array class that doesn’t own its data, just acts as a wrapper around T* and size_t (and give it bounds checking). Make a string class that wraps char buf[ N ]; and snprintf (_snprintf on Windows does not behave exactly like posix snprintf FYI). Use the SCOPE_EXIT trick for resource cleanup.Use casts that explode if you smash values by accident.
Some examples of code I have that I think is neat and shouldn’t upset C programmers, starting with a better printf:
It’s hard to give specific examples of the problems in action but 1) you can pigeonhole yourself into writing code that’s ugly/inefficient, 2) it encourages managing resources in the leaves of your code which can be inefficient and difficult to follow 3) it’s viral - any structs containing RAII types become RAII types themselves, and 4) once classes start having meaningful constructors and destructors it forces a lot of complexity everywhere else. Your allocators have to start calling constructors and destructors, you have to be careful with memcpys and memsets, and not having to worry about that 100% of the time is a big win.
I didn’t mean you should never use it, but I think it’s rarely actually a win.
I’m not following you at all. From your submission history you seem to do mostly graphics programming, and I can buy that there may not be much use for destructors when you avoid dynamic allocation inside loops.
Statements like “allocators have to start calling constructors and destructors” and “you have to be careful with memcpys and memsets” reinforce that you’re using C++ in some highly niche way. I don’t write allocators often enough that the amount of effort going into them is a data point for the language. And yes you certainly don’t want to be using memcpy in the presence of destructors.
I went looking around the internet for critiques of RAII (because in 20 years of C++ programming I’ve never encountered criticism of RAII before), and found very few:
None of these is very compelling at all.. So I’m still flummoxed. RAII is one of two reasons I use C++. My code samples there show that I don’t even use the class keyword when given the choice. But you’ll prise destructors from my cold dead hands.
From your submission history you seem to do mostly graphics programming, and I can buy that there may not be much use for destructors when you avoid dynamic allocation inside loops.
You’re right with that, but even in random glue code I find that RAII can be annoying sometimes. It’s hard to explain without having specific code examples, and I don’t run into it in my home code so I don’t have anything to share. I also expect that std::move helps with some of them, but solving problems with language features with more language features seems iffy to me and we don’t use C++11 at work.
you’re using C++ in some highly niche way.
I’ve been writing C for longer than C++, so I mostly write C and add C++ where I think it makes sense. So I guess you’re right there too.
I’ve been writing C for longer than C++, so I mostly write C and add C++ where I think it makes sense.
Same here, so I totally feel you.
I don’t use C++11 either[1]. So I don’t think that’s an issue here.
[1] On a tangent, my policy lately has been to move to a standard only when it’s the default for the version of the compiler shipped with the newest version of Linux and OS X (LTS in the case of Ubuntu). I often get questions from users on older versions of compilers, so this seems a reasonably conservative line in the sand. For small projects like mine, losing a single person who might have tried them out is a big loss. Ubuntu 16.04 and OS X Sierra (both released in 2016) neither enable C++11 by default. So I’m still on C++03. In a year or three I’ll migrate Mu to C++11 and start using auto and range loops.
“Don’t use the STL. Don’t use RAII. Don’t follow “everything has value semantics and a = b does massive allocations and copies”. Don’t use the STL. Don’t use smart pointers. … Maybe use the STL if you can’t be bothered to implement things yourself, but measure your build times before and after.”
Yes, definitely avoid exceptions if possible :)
RAII is one of the best things about C++.
This is amazing:
database_connection db;
if (db.open("127.0.0.1", port)) {
...
}
// Don't worry about closing the connection no matter how we return from this function it will close
RAII should be exactly the same performance as the same code in C. The only time it should differ is if the C code avoided calling the “destructor” function before some of the return statements. You might have been confusing RAII with garbage collection or shared_ptr etc.?
Compile times are good enough these days to use STL on all but the largest projects. Use STL until you find a place where it is slow.
C++ users need to learn about reference vs pointer vs array, and move semantics, and then basically just wrap all pointers in unique_ptr or shared_ptr (And learn the difference).
I would really try to avoid writing your own string class. Add some axillary functions to convert between your types to and from strings, sure. It is fun and isn’t too hard to create a string library, but in commercial stuff, there is a lot to be said of being able to bringing people into an organisation and having them be able to hack on the code in the first hour because they have used std::string/map/list/vector before.
A better printf, use either of these:
With streams you can do this type safely:
my_log<<"x="<<x<<", y="<<y<<std::endl;
With var args templates in C++11 you can do this also with type safety:
I added iostream/string/vector/map to every file in my project (80k LOC) with gcc -include and my debug build went from 2.7 to 4.7 seconds. Less bad than I was expecting but still not great.
unique_ptr is good but I find shared_ptr is a symptom of doing resource management in leaf code and losing track of/not caring what their lifetimes/owners are.
Not such a big deal if it takes 2 hours vs 1 IMO.
Format specifiers in streams are very very bad. They’re unreadable and they’re kept around as state on the stream to screw up your prints everywhere else when you forget to reset them.
Writing my_log_function like that is good and better than C’s printf because you can add your own types to it, but I prefer the printf format + args style. If you’re feeling ambitious you can actually write a monster print that combines both styles, see autoprintf.
Edit: if you do actually implement printing with templates it’s a big win to always disable optimisations to keep compile times down. If you want to do it cross-platform you need something like this.
SCOPE_EXIT is definitely better than normal C goto or while/break.
Yes shared_ptr can be a sign you are doing the wrong thing, especially if every single pointer in your application is a shared_ptr. In my code I find myself mostly using std::array, stack objects + references and occasionally unique_ptr which I am basically using as an “optional” return instead of returning a raw “null or object*” depending on if something was created or not.
Yes, 2 hours is not a big deal even in the first week the developer has 40 of them :) I just mean it is nice that most C++ programmers have experience with the STL or have at least heard of it and know the basic concepts. Creating a string class or the other containers is ok, but you have to work hard to make it sane, predictable, easy to work with. The string library is not perfect but it is better than string libraries I wrote 20 years ago :)
Yes format specifiers with state are the worst. You could possibly do something like:
While I do mostly agree. It could be argued that the resulting code is readable but not writable or maintainable unless the next coder is already a capable C(or worse, C++) programmer anyway.
I myself ignore these concerns and write Objective-C++ on occasions when I think the performance boost is worth it.
I think Standard ML ought to be considered a contender. It’s also a very small language, but can be compiled to extremely efficient code. It’s principal advantage is that there is a complete formal semantics for it—there is no “undefined behavior.” There isn’t a lot of passion for it these days but I often wonder if things would be nicer if it had caught on. I really like the module and functor system.
I think most of Standard ML’s good ideas are in more widespread use in the form of OCaml. Standard ML does have a full program optimizing compiler in the form of MLton, but I don’t think there’s an equivalent for OCaml. That being said, I’m not sure OCaml is as performant as C, and it’s also started to gather some cruft as it gets older. It would be interesting to see a “fresh start” ML-style language designed for this day and age.
Not quite whole program optimization but kinda close is the flambda version of the OCaml compiler. What would you consider cruft in OCaml? In my experience the cruftiest parts are actually the oldest parts, the newer things are quite alright.
I think that the functor syntax is a little messy, especially for multi-argument functors. Relatedly, the new support for first-class modules is great, but it’s a little hard to figure out the first time around. I also really wish that there was support for first class constructors (I think Standard ML has this), and though the new ppx stuff is really cool, I think the resulting extensions can be a little ugly.
Hmm, now that you mention it, these are all valid points, thanks. Curiously, even OCamls predecessor, Caml Light had first class constructors. For OCaml there are a number of “solutions” to this, but the fact that there are multiple already illustrates the fact they are missing from the core language.
It allows not only functional programming. Here the section “What is ATS good for?”:
ATS can greatly enforce precision in practical programming.
ATS can greatly facilitate refinement-based software development.
ATS allows the programmer to write efficient functional programs that directly manipulate native unboxed data representation.
ATS allows the programmer to reduce the memory footprint of a program by making use of linear types.
ATS allows the programmer to enhance the safety (and efficiency) of a program by making use of theorem-proving.
ATS allows the programmer to write safe low-level code that runs in OS kernels.
ATS can help teach type theory, demonstrating both convincingly and concretely the power and potential of types in constructing high-quality software.
Are the dependent types really that readable to people who have a hard time with C? I wouldn’t suggest it for ease of use unless talking to someone who worked with theorem provers complaining about speed of extracted code.
Let’s compare Haskell or OCaml to C and Python. The compiled FP languages are all going to be less code than C and better performance than Python. I would argue that they are likely to perform similarly to the C code, and they are likely to be less code than Python as well. I think most of the cost of FP is up-front: you have to learn it and figure out how to apply it. You can definitely make programs that outperform C, and it will usually be less work than writing them in C. But the tradeoffs are already set in a good place.
I think people tend to overestimate C’s performance. First, C is not the performance ceiling of all programs. Many programs are amenable to cheap parallelization, which is difficult to do in C but easy in Go or Haskell. Second, to write C is itself a lot of work. Getting to the same level of completion with C is often more work than getting there in other languages. The time you save getting to working with OK performance is time you can spend on improving performance of a working program with other languages.
What do you do if you get to completion with C and the program still doesn’t perform? You have to tear out a substantial chunk of C and rewrite it. But if you profile your code (which you should do anyway) you will probably find that only a small chunk of it actually has performance ramifications. This is why there are programs that are ostensibly written in Python that perform so well. It’s easy to profile the code, find the critical section, move that section out to C and use it as a library from some other language. Well, you can do that from OCaml or Haskell too. And often it is that glue code that unites your critical section with the real world, that is both tedious to write in C and error prone, as well as unlikely to matter from a performance standpoint.
I personally enjoy myrddin http://myrlang.org/. It has generics, type inference, tuples, pattern matching and bounds checking, so feels like a much more powerful C. Overall I think the only thing the language needs is some strong community building to help get the ball rolling better.
With capable people working on libraries, documentation, tutorials etc, I think it could build a reputation as a better C that is easier to learn than rust.
By “readable”, do you mean “good programmers can write extremely readable code” or “bad programmers cannot make a terrible mess of it”?
A language like C++ answer the first definition, but is worse than C on the second one.
Go is typically a language that tries to answer the second definition. Sure, it has a GC, but do you really want to let bad programmers manage memory anyway?
Personally, I think the second definition is more important than the first one. You can write pretty readable C code, look at Redis for instance. But you can also write things like OpenSSL. Or like the J language - but that style might be better for J programmers, see below.
“Readability” also depends a lot on what you already know. For me Japanese is harder to understand than English, but the Japanese think otherwise. Likewise Haskell programmers may think C is unreadable, and vice-versa.
At least Go (and, to a lesser extent, Python) has the advantage that it enforces uniformity within the language. So if you have worked on a Go project you should not be too lost when you look at another one. This is clearly not the case of C++ (or Perl).
You could make some easy, though shallow, improvements over C just by changing a few features of the syntax. I once prototyped such an improvement for a class project, published at github.com/roryokane/cs283-final-project. As the README explains, my compiler changed C syntax in three ways (inspired by Python and Ruby syntax):
no semicolons required at ends of lines
use indentation instead of braces {} for blocks
if, while, and for loops need no parentheses around their conditions
I am aware that indentation-sensitivity is a controversial feature. Each of these potential improvements are orthogonal and could be considered on their own.
IIRC LuaJIT is fast for math but not for string processing.
Lua code has very high level string operations like Python or Ruby, which the JIT can’t necessarily do anything about. String manipulation code often creates a lot of garbage. LuaJIT was supposed to have a new garbage collector but I believe it never got finished.
k/q is generally faster than C, and is highly readable.
However, it’s interesting that it’s not a compiled language, doesn’t do type annotations, has automatic memory management, etc, but is still faster than C.
This suggests to me that what we think of as one language (or implementation) being faster or slower than another, what we’re really thinking about is something else: It may be that when you are thinking in k/q, you’re able to write faster code, and indeed, my C code has changed dramatically since I learned it.
I don’t know much about k/q, but have heard from others that it is fast. But I’ve never seen any kind of benchmarks that I was particularly happy with. Do you know of any? Could I, for example (picking a pet project of mine), write a regex engine in k/q that competes with PCRE?
I’ve never seen any kind of benchmarks that I was particularly happy with. Do you know of any?
You can look at the stac benchmarks. They’re a set of specific (hard) problems that fully consider the hardware as a component of the solution.
Could I, for example (picking a pet project of mine), write a regex engine in k/q that competes with PCRE?
Maybe? I think it probably depends on how much you like parsing.
Let’s say you have a billion webpages to crawl, and you want to see which ones have /g.*o.*o.*g/in the title. If we start in the middle, and try to translate our knowledge of regular expressions into the business solution (finding a brand name in web pages), we might as well start on the second half.
CL-PCRE uses closures to implement regular expressions. I think a lot of languages do because it’s easy, and indeed implementing a scan as a series of compares sounds reasonable, but:
This idiom: final_state=initial_statetable/dictionary?input is actually common enough (3 operators) that most k/q programmers wouldn’t benefit much from PCRE, but because most people want to use regular expressions “because they already know it”, most kdb developers who need PCRE just simply link with PCRE, because dealing with all the corner cases of parsing is a lot of work.
Interesting. I do find it highly intriguing that you consider parsing to be the reason not to write your own regex engine. Parsing might be the hardest part of a naive regex engine, but it’s certainly not the hardest part of PCRE (or other production grade regex engines). A regex engine based on a simple state machine is unfortunately not going to perform nearly as well as PCRE. :-/
To clarify, my question was not, “how do I execute a regex in k/q” but more specifically, “could I write a regex engine in k/q that rivals the performance of PCRE.” I ask that because you claimed that k/q are just as fast as C. My question is meant to question whether you meant, “k/q is just as fast as C for programs that I write” or whether you meant “k/q can do anything C can do, but faster.” It’s an important difference!
You can look at the stac benchmarks. They’re a set of specific (hard) problems that fully consider the hardware as a component of the solution.
I clicked on a few of the entries, but I don’t know how to read them. :-/ I was hoping for a comparison between programs doing roughly equivalent work between k/q and C.
A regex engine based on a simple state machine is unfortunately not going to perform nearly as well as PCRE. :-/
And yet, my simple state machine in q completes google’s challenge around 1000x faster than the bigtable-based solution which uses PCRE, so if performance is desirable, we should not use PCRE!
How silly.
I ask that because you claimed that k/q are just as fast as C. My question is meant to question whether you meant, “k/q is just as fast as C for programs that I write” or whether you meant “k/q can do anything C can do, but faster.” It’s an important difference!
Actually no.
I said k/q are generally faster than C.
I meant that k/q [programs] are generally [written and executed] faster than C [programs are].
Consider it is possible to write a program in C and assembly that executes as quickly as the k/q solution by also writing k/q. There may be inefficiencies looking at both together will reveal, and that then by removing some q and implementing some more C or more assembly you will find a solution that executes quicker still. This process can clearly be continued. This process is clearly true for any pair of languages.
So what good is this definition?
Are you merely playing or trolling? Trying to look at microbenchmarks is an amusement, but doing it too much will just give you myopia.
I was hoping for a comparison between programs doing roughly equivalent work between k/q and C.
I think you were hoping for a simple comparison. Databases are very complicated, so it is very difficult to figure out why kdb (an SQL implementation written in q) is faster than every other SQL engine written in C (sometimes by 1000x or more!). However it can be very clear when you understand k/q, which is why I simply recommend learning that instead.
And yet, my simple state machine in q completes google’s challenge around 1000x faster than the bigtable-based solution which uses PCRE, so if performance is desirable, we should not use PCRE!
Can you show me enough data so that I can reproduce this result myself?
Are you merely playing or trolling?
I’m looking for data. You’ve provided none that I’m capable of reading and interpreting the results for myself. Instead, you chose to accuse me of trolling.
What I’ve learned from this exchange is that I should not ask geocar any questions in the future.
You’ve provided none that I’m capable of reading and interpreting the results for myself.
And yet you somehow think this is my fault.
Other people have no trouble with the stac reports, so I assume with a little googling you can find someone to help you read and interpret benchmarks if that’s what you’re actually interested in doing.
you chose to accuse me of trolling
I did nothing of the sort. I asked. Some people think slightly twisting someone’s words to mean something completely different is entertainment. Others twist words accidentally because they are not thinking clearly.
What I’ve learned from this exchange is that I should not ask geocar any questions in the future.
That’s a sad thing to learn.
When I am faced with something I do not understand, I try to understand it. Sometimes this can be difficult for me when what people are saying sounds strange. Something that helps me with this is the principle of charity, then I can think about what they are saying and how it could be true, and when I find such a way then I better understand what they are saying.
What do you expect? I was (and still am) interested in learning about the performance characteristics of k/q. I asked for data. I got a link to a web site that asked me to register. I did that (after giving out a fake phone number). I downloaded a couple of reports. None of them were enlightening. They don’t relate the data to anything I already know, that is, I don’t have the background to understand them, and I don’t have several days to spare to understand them. Here’s my initial request:
But I’ve never seen any kind of benchmarks that I was particularly happy with. Do you know of any?
Let’s throw these stac reports into the pile of benchmarks I’m not happy with. Perhaps there are no other benchmarks and the answer to my question is a simple “no.” My only other recourse is to go out and actually learn k/q and do the analysis myself. That would take me at least weeks to do, but more likely, months. I’m willing to invest a few hours reading a detailed performance analysis that compares k/q to something else I know, but I’m not willing to spend months of my time to gain that information. This is a perfectly reasonable ask in my opinion. People have done it for other languages, so I don’t see why it can’t be done for k/q.
Even if benchmarks are hard to come by (and apparently they are), another way I could relate to k/q’s performance characteristics is by understanding what it would take to build something that I am very familiar with (regex engines), but are typically written in C. You responded with a presumably well meaning answer, but it was certainly not the answer to my question. I tried to say as much, but you responded with some anecdote about Google BigTable. Huh?
What do I get in exchange for asking these questions? I get a lecture from you about amusements and myopia and a subsequent accusation that I am either “playing” or “trolling” (which, from my perspective, is not much different than just accusing me of trolling). In other words, I get a big ol’ song and dance from you. Indeed, the principle of charity would have helped quite a bit, don’t you think?
Let’s throw these stac reports into the pile of benchmarks I’m not happy with.
Let’s throw everything that you don’t understand into the pile of things you’re not happy with. Other people are happy with them, so I think they’re good enough, but fine: you’re not.
Why exactly do you think it’s my responsibility to make you happy?
I’m not willing to spend months of my time to [learn something I don’t understand]
If you don’t understand something, why exactly do you think that’s my fault?
another way I could relate to k/q’s performance characteristics is by understanding what it would take to build something that I am very familiar with (regex engines), but are typically written in C.
Ah, well, except you’re not willing to learn k/q, so what you’re telling me is what I’m already suspecting.
Some people think slightly twisting someone’s words to mean something completely different is entertainment. Others twist words accidentally because they are not thinking clearly.
For some values of “nearly” and “more readable”, of course. Your question doesn’t have any definite answer as far as my knowledge goes but Go surely fixes a lot of syntax clutter. It also makes it easier to use arrays than linked lists.
Arguably C++ could qualify if you are one of those people who find it intuitive and easy to read.
Any new compiler has this issue. Even Clang C is slower than GCC C, purely because people have added micro-optimization after micro-optimization to GCC for decades. Until a compiler has compiled everything under the sun, AND had all the resulting performance bugs reported, AND someone has fixed all those bugs, it will be slower than any older compiler that’s well maintained, especially for edge cases.
To answer the original question, I think it will be something like Rust, a language extremely aggressive about constraints, so the optimizer can actually “know” what you mean to do and optimize around that. I don’t know if it will be Rust, I suspect it might be whatever comes after Rust when people have sorted out how to make some of the Rust features more accessible.
Anything Wirth made without the upper case. Compiler optimizations C got put into it instead would’ve made them super-fast, too. First of these done in OS’s was Modula-2.
EDIT to add: PreScheme was a low-level LISP designed to compile to efficient, machine code. Initially targeted C to benefit from optimized compilers. A C-ish set of operators for PreScheme with macros or front end giving it a nice syntax could fit the bill. Julia language did that with femtolisp at its core. People also mention Nim a lot when I talk readable, macro’d, fast languages.
Domain/OS Design Principles (pdf) Although looking through that now, there’s not a lot about Pascal per se. I used Apollo’s for several years and really enjoyed developing applications for them.
Hmm. Wikipedia says it’s written in Pascal but I didn’t see it straight-forward say it’s written in Pascal in that document you linked. The references I found with Ctrl-F were descriptions of what Pascal stuff looks like, a reference to interface language able to generate it, and support for Pascal I/O. I don’t doubt it as numerous OS’s or low-level components were done in Pascal in that time. Glad you gave it to me as it was a nice reminder of how advanced Apollo’s stuff was. People told me before. I either just learned or got a reminder that it was a big influence of CORBA, too. Memory too bad to be sure.
I will note that the Lilith project started in the 70’s to end with final product in early-to-mid 80’s. So, it’s hard to say which were first between Modula-2 and Pascal. Most important is both were happening with Wirth-style languages in similar time period. I’m still curious when first, Pascal-based OS was released for Apollo given the other one was Wirth and Jurg themselves doing it.
Aren’t the safety features designed to carry zero runtime cost? The costs are incurred at compile time, so I fail to see how performance of Rust programs would suffer compared to C due to any added safety.
The language does let you write the most performant solution, you just might need to use unsafe, depending on what problem you’re trying to solve. I do a lot work in Rust on text search and parsing, for example, and I rarely if ever need to write unsafe to get performance that is comparable to C/C++ programs that do similar work. On the contrary, I wrote some Rust code that does compression/decompression, and in order to match the performance of the C++ reference implementation, I had to use quite a bit of unsafe.
And like, there’s mundane stuff like bounds checking.
Well, firstly, bounds checking is trivial to disable on a case-by-case basis (again, using unsafe). Secondly, bounds checking so rarely makes a difference at all. Notice that the OP asked “nearly as fast as C.” I think bounds checking meets that criteria.
I wrote some Rust code that does compression/decompression, and in order to match the performance of the C++ reference implementation, I had to use quite a bit of unsafe.
Why specifically? That kind of code is common in real world. Figuring out how to do it safely in Rust or with an external tool is worthwhile.
Unaligned loads and stores. Making it safe seems possible, but probably requires doing a dance with the code generator. That’s the nice part about Rust. If I want to do something explicit and not rely on the code generator, I have the option to do so.
It’s worth pointing out that the library encapsulates unsafe, so that users of the library need not worry about it (unless there’s a bug). That’s the real win IMO.
That makes sense. I recall that some projects mocked up assembly in their safe language or prover giving the registers and so on types to detect errors. You thought about doing such a routine in a Rust version of x86 assembly to see if borrow checker or other typing can catch problems? Or would that be totally useless you think?
It sounds interesting, and I hope someone works on it, but it’s not personally my cup of tea. :-) (Although, I might try to use it if someone else did the hard work building it! :P)
I’ll keep that in mind. For your enjoyment, here’s an example of what’s possible that’s more specialist rather than just embedding in Rust or SPARK. They go into nice detail, though, of what benefits it brought to Intel assembly, though.
They also had a compiler from a safe subset of C (Popcorn) to that language so one could mix high-level and low-level code maintaining safety and possibly knocking out abstraction gaps in it.
Bounds checking almost never matters on modern CPUs. Even ignoring the fact that Rust can often lift the bounds-check operation so it’s not in the inner loop, the bounds-check almost never fails, so the branch predictor will just blow right past. It might add one or two cycles per loop, but maybe not.
I didn’t mention it for “more readable code” as question asked. Rust has a steep learning curve like Ada does. I didn’t mention it either. Neither seems to fit that requirement.
Lots most of my memory to a head injury. It’s one of those things I don’t remember. I’m going to have to relearn one or both eventually. I have a concept called Brute Force Assurance that tries to reduce verification from an expensive, expert problem to a pile of cheap labor problem. The idea is a language like Scheme or Nim expresses the algorithms in lowest-common denominator form that’s automatically converted to equivalent C, Rust, Ada, and SPARK. Their tools… esp static analyzers or test generators in C, borrow-checker in Rust, and SPARK’s prover… all run on the generated code. Errors found there are compared against the original program with it changed for valid errors. Iterate until no errors and then move on to next work item.
I’ll probably need to either have language experts doing it together or know the stuff myself when attempting it. I’m especially interested in how much a problem it will be if underlying representations, calling conventions or whatever are different. And how that might be resolved without modifying the compilers by just mocking it up in the languages somehow. Anyway, I’ll have to dig into at least Rust in the future since I probably can’t recreate a borrow-checker. Macroing to one from a Wirth- or Scheme-simple language will be much easier.
It would have explicit conversions required, overridable prefix conversion operators distinguishing between failable and nonfailable, some convenient error handling syntax, tagged union types, and at least basic templates and overloading.
It’s been 9 hours and nobody has mentioned C++!
I’m not suggesting you jump off the C++ cliff. Don’t use the STL. Don’t use RAII. Don’t follow “everything has value semantics and
a = b
does massive allocations and copies”. Don’t use the STL. Don’t use smart pointers. Don’t use exceptions - you can disable them at compile time. Maybe use the STL if you can’t be bothered to implement things yourself, but measure your build times before and after.Start with C, and introduce C++ features when they let you write the same C code but more concisely/safely. Use operator overloading for mathsy types. Add an array class that doesn’t own its data, just acts as a wrapper around
T*
andsize_t
(and give it bounds checking). Make a string class that wrapschar buf[ N ];
andsnprintf
(_snprintf
on Windows does not behave exactly like posixsnprintf
FYI). Use theSCOPE_EXIT
trick for resource cleanup. Use casts that explode if you smash values by accident.Some examples of code I have that I think is neat and shouldn’t upset C programmers, starting with a better printf:
a better string class (no allocations):
a wrapper around Lua’s string matching code (no allocations!):
Isn’t RAII a hugely important technique for avoiding code problems?
Until it causes different problems, yes ^^;
Like what?
It’s hard to give specific examples of the problems in action but 1) you can pigeonhole yourself into writing code that’s ugly/inefficient, 2) it encourages managing resources in the leaves of your code which can be inefficient and difficult to follow 3) it’s viral - any structs containing RAII types become RAII types themselves, and 4) once classes start having meaningful constructors and destructors it forces a lot of complexity everywhere else. Your allocators have to start calling constructors and destructors, you have to be careful with memcpys and memsets, and not having to worry about that 100% of the time is a big win.
I didn’t mean you should never use it, but I think it’s rarely actually a win.
I’m not following you at all. From your submission history you seem to do mostly graphics programming, and I can buy that there may not be much use for destructors when you avoid dynamic allocation inside loops.
Statements like “allocators have to start calling constructors and destructors” and “you have to be careful with
memcpy
s andmemset
s” reinforce that you’re using C++ in some highly niche way. I don’t write allocators often enough that the amount of effort going into them is a data point for the language. And yes you certainly don’t want to be usingmemcpy
in the presence of destructors.I went looking around the internet for critiques of RAII (because in 20 years of C++ programming I’ve never encountered criticism of RAII before), and found very few:
http://coliveira.net/software/raii-is-overrated (2011)
https://en.sfml-dev.org/forums/index.php?topic=9359.0 (2012) https://en.sfml-dev.org/forums/index.php?topic=19788.0 (2016)
None of these is very compelling at all.. So I’m still flummoxed. RAII is one of two reasons I use C++. My code samples there show that I don’t even use the
class
keyword when given the choice. But you’ll prise destructors from my cold dead hands.You’re right with that, but even in random glue code I find that RAII can be annoying sometimes. It’s hard to explain without having specific code examples, and I don’t run into it in my home code so I don’t have anything to share. I also expect that
std::move
helps with some of them, but solving problems with language features with more language features seems iffy to me and we don’t use C++11 at work.I’ve been writing C for longer than C++, so I mostly write C and add C++ where I think it makes sense. So I guess you’re right there too.
Same here, so I totally feel you.
I don’t use C++11 either[1]. So I don’t think that’s an issue here.
[1] On a tangent, my policy lately has been to move to a standard only when it’s the default for the version of the compiler shipped with the newest version of Linux and OS X (LTS in the case of Ubuntu). I often get questions from users on older versions of compilers, so this seems a reasonably conservative line in the sand. For small projects like mine, losing a single person who might have tried them out is a big loss. Ubuntu 16.04 and OS X Sierra (both released in 2016) neither enable C++11 by default. So I’m still on C++03. In a year or three I’ll migrate Mu to C++11 and start using
auto
and range loops.As you say, to get by as “readable” you have to avoid a lot…
If you need a language that is fast as can be and type safe, D is much nicer and more readable than C++.
It’s like C++ but you can ask the compiler what it knows about any symbol and act on that knowledge at compile time.
Template meta programming in C++ is a complete nightmare… You can even ask the guy who invented template meta-programming and wrote The Book on it.
He went off and joined D instead since template meta-programming is simple, readable and straightforward in D.
Yes, definitely avoid exceptions if possible :)
This is amazing:
RAII should be exactly the same performance as the same code in C. The only time it should differ is if the C code avoided calling the “destructor” function before some of the return statements. You might have been confusing RAII with garbage collection or shared_ptr etc.?
A better printf, use either of these:
With streams you can do this type safely:
With var args templates in C++11 you can do this also with type safety:
I added iostream/string/vector/map to every file in my project (80k LOC) with
gcc -include
and my debug build went from 2.7 to 4.7 seconds. Less bad than I was expecting but still not great.unique_ptr is good but I find shared_ptr is a symptom of doing resource management in leaf code and losing track of/not caring what their lifetimes/owners are.
Not such a big deal if it takes 2 hours vs 1 IMO.
Format specifiers in streams are very very bad. They’re unreadable and they’re kept around as state on the stream to screw up your prints everywhere else when you forget to reset them.
Writing
my_log_function
like that is good and better than C’s printf because you can add your own types to it, but I prefer the printf format + args style. If you’re feeling ambitious you can actually write a monster print that combines both styles, see autoprintf.Edit: if you do actually implement printing with templates it’s a big win to always disable optimisations to keep compile times down. If you want to do it cross-platform you need something like this.
SCOPE_EXIT is definitely better than normal C goto or while/break.
Yes shared_ptr can be a sign you are doing the wrong thing, especially if every single pointer in your application is a shared_ptr. In my code I find myself mostly using std::array, stack objects + references and occasionally unique_ptr which I am basically using as an “optional” return instead of returning a raw “null or object*” depending on if something was created or not.
Yes format specifiers with state are the worst. You could possibly do something like:
Or:
I think I prefer the second one.
Thanks for the optimisation pragma tips!
While I do mostly agree. It could be argued that the resulting code is readable but not writable or maintainable unless the next coder is already a capable C(or worse, C++) programmer anyway.
I myself ignore these concerns and write Objective-C++ on occasions when I think the performance boost is worth it.
Crystal http://crystal-lang.org. It’s a Ruby inspired syntax, but compiles to code that’s comparable to C.
I was ready for the link to go to crystal lang homepage
I think Standard ML ought to be considered a contender. It’s also a very small language, but can be compiled to extremely efficient code. It’s principal advantage is that there is a complete formal semantics for it—there is no “undefined behavior.” There isn’t a lot of passion for it these days but I often wonder if things would be nicer if it had caught on. I really like the module and functor system.
I think most of Standard ML’s good ideas are in more widespread use in the form of OCaml. Standard ML does have a full program optimizing compiler in the form of MLton, but I don’t think there’s an equivalent for OCaml. That being said, I’m not sure OCaml is as performant as C, and it’s also started to gather some cruft as it gets older. It would be interesting to see a “fresh start” ML-style language designed for this day and age.
Not quite whole program optimization but kinda close is the
flambda
version of the OCaml compiler. What would you consider cruft in OCaml? In my experience the cruftiest parts are actually the oldest parts, the newer things are quite alright.I think that the functor syntax is a little messy, especially for multi-argument functors. Relatedly, the new support for first-class modules is great, but it’s a little hard to figure out the first time around. I also really wish that there was support for first class constructors (I think Standard ML has this), and though the new
ppx
stuff is really cool, I think the resulting extensions can be a little ugly.Hmm, now that you mention it, these are all valid points, thanks. Curiously, even OCamls predecessor, Caml Light had first class constructors. For OCaml there are a number of “solutions” to this, but the fact that there are multiple already illustrates the fact they are missing from the core language.
Especially with MLton compiler.
[Comment removed by author]
Maybe http://www.ats-lang.org/ ?
It allows not only functional programming. Here the section “What is ATS good for?”:
ATS can greatly enforce precision in practical programming. ATS can greatly facilitate refinement-based software development. ATS allows the programmer to write efficient functional programs that directly manipulate native unboxed data representation. ATS allows the programmer to reduce the memory footprint of a program by making use of linear types. ATS allows the programmer to enhance the safety (and efficiency) of a program by making use of theorem-proving. ATS allows the programmer to write safe low-level code that runs in OS kernels. ATS can help teach type theory, demonstrating both convincingly and concretely the power and potential of types in constructing high-quality software.
Are the dependent types really that readable to people who have a hard time with C? I wouldn’t suggest it for ease of use unless talking to someone who worked with theorem provers complaining about speed of extracted code.
[Comment removed by author]
Probably even more so for 8-bit coders who might not imagine a world past assembly or C. Much less ATS on their hardware.
https://github.com/fpiot/arduino-ats
[Comment removed by author]
So there’s a lot of fine print on this.
Let’s compare Haskell or OCaml to C and Python. The compiled FP languages are all going to be less code than C and better performance than Python. I would argue that they are likely to perform similarly to the C code, and they are likely to be less code than Python as well. I think most of the cost of FP is up-front: you have to learn it and figure out how to apply it. You can definitely make programs that outperform C, and it will usually be less work than writing them in C. But the tradeoffs are already set in a good place.
I think people tend to overestimate C’s performance. First, C is not the performance ceiling of all programs. Many programs are amenable to cheap parallelization, which is difficult to do in C but easy in Go or Haskell. Second, to write C is itself a lot of work. Getting to the same level of completion with C is often more work than getting there in other languages. The time you save getting to working with OK performance is time you can spend on improving performance of a working program with other languages.
What do you do if you get to completion with C and the program still doesn’t perform? You have to tear out a substantial chunk of C and rewrite it. But if you profile your code (which you should do anyway) you will probably find that only a small chunk of it actually has performance ramifications. This is why there are programs that are ostensibly written in Python that perform so well. It’s easy to profile the code, find the critical section, move that section out to C and use it as a library from some other language. Well, you can do that from OCaml or Haskell too. And often it is that glue code that unites your critical section with the real world, that is both tedious to write in C and error prone, as well as unlikely to matter from a performance standpoint.
D, familiar , yet far more expressive, reusable.
auto foo = [1,2,3]
- light on type annotations in some ways.I personally enjoy myrddin http://myrlang.org/. It has generics, type inference, tuples, pattern matching and bounds checking, so feels like a much more powerful C. Overall I think the only thing the language needs is some strong community building to help get the ball rolling better.
With capable people working on libraries, documentation, tutorials etc, I think it could build a reputation as a better C that is easier to learn than rust.
https://nim-lang.org/
By “readable”, do you mean “good programmers can write extremely readable code” or “bad programmers cannot make a terrible mess of it”?
A language like C++ answer the first definition, but is worse than C on the second one.
Go is typically a language that tries to answer the second definition. Sure, it has a GC, but do you really want to let bad programmers manage memory anyway?
Personally, I think the second definition is more important than the first one. You can write pretty readable C code, look at Redis for instance. But you can also write things like OpenSSL. Or like the J language - but that style might be better for J programmers, see below.
“Readability” also depends a lot on what you already know. For me Japanese is harder to understand than English, but the Japanese think otherwise. Likewise Haskell programmers may think C is unreadable, and vice-versa.
At least Go (and, to a lesser extent, Python) has the advantage that it enforces uniformity within the language. So if you have worked on a Go project you should not be too lost when you look at another one. This is clearly not the case of C++ (or Perl).
You could make some easy, though shallow, improvements over C just by changing a few features of the syntax. I once prototyped such an improvement for a class project, published at github.com/roryokane/cs283-final-project. As the README explains, my compiler changed C syntax in three ways (inspired by Python and Ruby syntax):
{}
for blocksif
,while
, andfor
loops need no parentheses around their conditionsI am aware that indentation-sensitivity is a controversial feature. Each of these potential improvements are orthogonal and could be considered on their own.
You can see an example of real code with this syntax in the file test/test-cases/k&r-cat-v2.betterc, which is the equivalent of the C code in k&r-cat-v2.c.
[Comment removed by author]
IIRC LuaJIT is fast for math but not for string processing.
Lua code has very high level string operations like Python or Ruby, which the JIT can’t necessarily do anything about. String manipulation code often creates a lot of garbage. LuaJIT was supposed to have a new garbage collector but I believe it never got finished.
[Comment removed by author]
k/q is generally faster than C, and is highly readable.
However, it’s interesting that it’s not a compiled language, doesn’t do type annotations, has automatic memory management, etc, but is still faster than C.
This suggests to me that what we think of as one language (or implementation) being faster or slower than another, what we’re really thinking about is something else: It may be that when you are thinking in k/q, you’re able to write faster code, and indeed, my C code has changed dramatically since I learned it.
mikejsavage’s examples written in q could be:
I don’t know much about
k
/q
, but have heard from others that it is fast. But I’ve never seen any kind of benchmarks that I was particularly happy with. Do you know of any? Could I, for example (picking a pet project of mine), write a regex engine ink
/q
that competes with PCRE?You can look at the stac benchmarks. They’re a set of specific (hard) problems that fully consider the hardware as a component of the solution.
Maybe? I think it probably depends on how much you like parsing.
Let’s say you have a billion webpages to crawl, and you want to see which ones have
/g.*o.*o.*g/
in the title. If we start in the middle, and try to translate our knowledge of regular expressions into the business solution (finding a brand name in web pages), we might as well start on the second half.CL-PCRE uses closures to implement regular expressions. I think a lot of languages do because it’s easy, and indeed implementing a scan as a series of compares sounds reasonable, but:
isn’t very fast. However in k/q, function application and array indexing is the same, so you might just write a transition table directly:
and then classify the input (a highly parallelizable operation) with find:
This idiom: final_state
=
initial_state table/
dictionary?
input is actually common enough (3 operators) that most k/q programmers wouldn’t benefit much from PCRE, but because most people want to use regular expressions “because they already know it”, most kdb developers who need PCRE just simply link with PCRE, because dealing with all the corner cases of parsing is a lot of work.Interesting. I do find it highly intriguing that you consider parsing to be the reason not to write your own regex engine. Parsing might be the hardest part of a naive regex engine, but it’s certainly not the hardest part of PCRE (or other production grade regex engines). A regex engine based on a simple state machine is unfortunately not going to perform nearly as well as PCRE. :-/
To clarify, my question was not, “how do I execute a regex in
k
/q
” but more specifically, “could I write a regex engine ink
/q
that rivals the performance of PCRE.” I ask that because you claimed thatk
/q
are just as fast as C. My question is meant to question whether you meant, “k
/q
is just as fast as C for programs that I write” or whether you meant “k
/q
can do anything C can do, but faster.” It’s an important difference!I clicked on a few of the entries, but I don’t know how to read them. :-/ I was hoping for a comparison between programs doing roughly equivalent work between
k
/q
and C.And yet, my simple state machine in q completes google’s challenge around 1000x faster than the bigtable-based solution which uses PCRE, so if performance is desirable, we should not use PCRE!
How silly.
Actually no.
I said k/q are generally faster than C.
I meant that k/q [programs] are generally [written and executed] faster than C [programs are].
Consider it is possible to write a program in C and assembly that executes as quickly as the k/q solution by also writing k/q. There may be inefficiencies looking at both together will reveal, and that then by removing some q and implementing some more C or more assembly you will find a solution that executes quicker still. This process can clearly be continued. This process is clearly true for any pair of languages.
So what good is this definition?
Are you merely playing or trolling? Trying to look at microbenchmarks is an amusement, but doing it too much will just give you myopia.
I think you were hoping for a simple comparison. Databases are very complicated, so it is very difficult to figure out why kdb (an SQL implementation written in q) is faster than every other SQL engine written in C (sometimes by 1000x or more!). However it can be very clear when you understand k/q, which is why I simply recommend learning that instead.
Can you show me enough data so that I can reproduce this result myself?
I’m looking for data. You’ve provided none that I’m capable of reading and interpreting the results for myself. Instead, you chose to accuse me of trolling.
What I’ve learned from this exchange is that I should not ask geocar any questions in the future.
And yet you somehow think this is my fault.
Other people have no trouble with the stac reports, so I assume with a little googling you can find someone to help you read and interpret benchmarks if that’s what you’re actually interested in doing.
I did nothing of the sort. I asked. Some people think slightly twisting someone’s words to mean something completely different is entertainment. Others twist words accidentally because they are not thinking clearly.
That’s a sad thing to learn.
When I am faced with something I do not understand, I try to understand it. Sometimes this can be difficult for me when what people are saying sounds strange. Something that helps me with this is the principle of charity, then I can think about what they are saying and how it could be true, and when I find such a way then I better understand what they are saying.
Good luck.
What do you expect? I was (and still am) interested in learning about the performance characteristics of
k
/q
. I asked for data. I got a link to a web site that asked me to register. I did that (after giving out a fake phone number). I downloaded a couple of reports. None of them were enlightening. They don’t relate the data to anything I already know, that is, I don’t have the background to understand them, and I don’t have several days to spare to understand them. Here’s my initial request:Let’s throw these stac reports into the pile of benchmarks I’m not happy with. Perhaps there are no other benchmarks and the answer to my question is a simple “no.” My only other recourse is to go out and actually learn
k
/q
and do the analysis myself. That would take me at least weeks to do, but more likely, months. I’m willing to invest a few hours reading a detailed performance analysis that comparesk
/q
to something else I know, but I’m not willing to spend months of my time to gain that information. This is a perfectly reasonable ask in my opinion. People have done it for other languages, so I don’t see why it can’t be done fork
/q
.Even if benchmarks are hard to come by (and apparently they are), another way I could relate to
k
/q
’s performance characteristics is by understanding what it would take to build something that I am very familiar with (regex engines), but are typically written in C. You responded with a presumably well meaning answer, but it was certainly not the answer to my question. I tried to say as much, but you responded with some anecdote about Google BigTable. Huh?What do I get in exchange for asking these questions? I get a lecture from you about amusements and myopia and a subsequent accusation that I am either “playing” or “trolling” (which, from my perspective, is not much different than just accusing me of trolling). In other words, I get a big ol’ song and dance from you. Indeed, the principle of charity would have helped quite a bit, don’t you think?
Let’s throw everything that you don’t understand into the pile of things you’re not happy with. Other people are happy with them, so I think they’re good enough, but fine: you’re not.
Why exactly do you think it’s my responsibility to make you happy?
If you don’t understand something, why exactly do you think that’s my fault?
Ah, well, except you’re not willing to learn k/q, so what you’re telling me is what I’m already suspecting.
Have a good day.
Go is improved C in countless aspects, discard anything undesired.
except for one
For some values of “nearly” and “more readable”, of course. Your question doesn’t have any definite answer as far as my knowledge goes but Go surely fixes a lot of syntax clutter. It also makes it easier to use arrays than linked lists.
Arguably C++ could qualify if you are one of those people who find it intuitive and easy to read.
Any new compiler has this issue. Even Clang C is slower than GCC C, purely because people have added micro-optimization after micro-optimization to GCC for decades. Until a compiler has compiled everything under the sun, AND had all the resulting performance bugs reported, AND someone has fixed all those bugs, it will be slower than any older compiler that’s well maintained, especially for edge cases.
To answer the original question, I think it will be something like Rust, a language extremely aggressive about constraints, so the optimizer can actually “know” what you mean to do and optimize around that. I don’t know if it will be Rust, I suspect it might be whatever comes after Rust when people have sorted out how to make some of the Rust features more accessible.
Anything Wirth made without the upper case. Compiler optimizations C got put into it instead would’ve made them super-fast, too. First of these done in OS’s was Modula-2.
EDIT to add: PreScheme was a low-level LISP designed to compile to efficient, machine code. Initially targeted C to benefit from optimized compilers. A C-ish set of operators for PreScheme with macros or front end giving it a nice syntax could fit the bill. Julia language did that with femtolisp at its core. People also mention Nim a lot when I talk readable, macro’d, fast languages.
re: first in PS
I’m pretty certain Apollo’s use of Pascal as its systems programming language for its OS predates the use of Modula-2 for an OS.
I had no idea they did the OS in Pascal. Now I got some more digging to use. Thanks for the tip. :)
Domain/OS Design Principles (pdf) Although looking through that now, there’s not a lot about Pascal per se. I used Apollo’s for several years and really enjoyed developing applications for them.
https://drive.google.com/file/d/1uEFAg8_5jlwmFyuo4fnGH0Lx__1BKZJJflcoB2_if5bbAb624OANfoozmNS-RWToRaW9EVoW-ZToPdYT/view?usp=drivesdk
Hmm. Wikipedia says it’s written in Pascal but I didn’t see it straight-forward say it’s written in Pascal in that document you linked. The references I found with Ctrl-F were descriptions of what Pascal stuff looks like, a reference to interface language able to generate it, and support for Pascal I/O. I don’t doubt it as numerous OS’s or low-level components were done in Pascal in that time. Glad you gave it to me as it was a nice reminder of how advanced Apollo’s stuff was. People told me before. I either just learned or got a reminder that it was a big influence of CORBA, too. Memory too bad to be sure.
I will note that the Lilith project started in the 70’s to end with final product in early-to-mid 80’s. So, it’s hard to say which were first between Modula-2 and Pascal. Most important is both were happening with Wirth-style languages in similar time period. I’m still curious when first, Pascal-based OS was released for Apollo given the other one was Wirth and Jurg themselves doing it.
Rust without safety features.
Aren’t the safety features designed to carry zero runtime cost? The costs are incurred at compile time, so I fail to see how performance of Rust programs would suffer compared to C due to any added safety.
They have runtime cost because the language won’t let you write the most performant solution. And like, there’s mundane stuff like bounds checking.
The language does let you write the most performant solution, you just might need to use
unsafe
, depending on what problem you’re trying to solve. I do a lot work in Rust on text search and parsing, for example, and I rarely if ever need to writeunsafe
to get performance that is comparable to C/C++ programs that do similar work. On the contrary, I wrote some Rust code that does compression/decompression, and in order to match the performance of the C++ reference implementation, I had to use quite a bit ofunsafe
.Well, firstly, bounds checking is trivial to disable on a case-by-case basis (again, using
unsafe
). Secondly, bounds checking so rarely makes a difference at all. Notice that the OP asked “nearly as fast as C.” I think bounds checking meets that criteria.Why specifically? That kind of code is common in real world. Figuring out how to do it safely in Rust or with an external tool is worthwhile.
Unaligned loads and stores. Making it safe seems possible, but probably requires doing a dance with the code generator. That’s the nice part about Rust. If I want to do something explicit and not rely on the code generator, I have the option to do so.
It’s worth pointing out that the library encapsulates unsafe, so that users of the library need not worry about it (unless there’s a bug). That’s the real win IMO.
That makes sense. I recall that some projects mocked up assembly in their safe language or prover giving the registers and so on types to detect errors. You thought about doing such a routine in a Rust version of x86 assembly to see if borrow checker or other typing can catch problems? Or would that be totally useless you think?
It sounds interesting, and I hope someone works on it, but it’s not personally my cup of tea. :-) (Although, I might try to use it if someone else did the hard work building it! :P)
I’ll keep that in mind. For your enjoyment, here’s an example of what’s possible that’s more specialist rather than just embedding in Rust or SPARK. They go into nice detail, though, of what benefits it brought to Intel assembly, though.
https://lobste.rs/s/kc2brf/typed_assembly_language_1998
They also had a compiler from a safe subset of C (Popcorn) to that language so one could mix high-level and low-level code maintaining safety and possibly knocking out abstraction gaps in it.
“typed assembly language” makes me think of LLVM IR :P
Bounds checking almost never matters on modern CPUs. Even ignoring the fact that Rust can often lift the bounds-check operation so it’s not in the inner loop, the bounds-check almost never fails, so the branch predictor will just blow right past. It might add one or two cycles per loop, but maybe not.
Or, in many cases: Rust.
I didn’t mention it for “more readable code” as question asked. Rust has a steep learning curve like Ada does. I didn’t mention it either. Neither seems to fit that requirement.
Can you compare and contrast your own personal learning experience between Ada and Rust?
Lots most of my memory to a head injury. It’s one of those things I don’t remember. I’m going to have to relearn one or both eventually. I have a concept called Brute Force Assurance that tries to reduce verification from an expensive, expert problem to a pile of cheap labor problem. The idea is a language like Scheme or Nim expresses the algorithms in lowest-common denominator form that’s automatically converted to equivalent C, Rust, Ada, and SPARK. Their tools… esp static analyzers or test generators in C, borrow-checker in Rust, and SPARK’s prover… all run on the generated code. Errors found there are compared against the original program with it changed for valid errors. Iterate until no errors and then move on to next work item.
I’ll probably need to either have language experts doing it together or know the stuff myself when attempting it. I’m especially interested in how much a problem it will be if underlying representations, calling conventions or whatever are different. And how that might be resolved without modifying the compilers by just mocking it up in the languages somehow. Anyway, I’ll have to dig into at least Rust in the future since I probably can’t recreate a borrow-checker. Macroing to one from a Wirth- or Scheme-simple language will be much easier.
It would have explicit conversions required, overridable prefix conversion operators distinguishing between failable and nonfailable, some convenient error handling syntax, tagged union types, and at least basic templates and overloading.
At least that’s without doing anything fancy.
Since everybody is mentioning his favorite language, here is mine: D.
If the “small runtime” aspect of C is important, then use D’s
-betterC
parameter.