I was recently musing about this on Mastodon[0], commenting on a tweet that said something to the effect of "C++ isn't complex, it just doesn't lie about the complexity of reality". I think Rust is similar, and really any system language has too be. However, there's a space between lying about reality and fully reflecting reality where useful abstractions, such as Rust's lifetimes live. If you hardline the "lying about reality" framing, any abstractions on top of machine code must be ruled out. So while I see where you are coming from, I think lots of space to improve system programming, as Rust did.
> What's really lacking are safe, easy, strong typed general purpose languages that can leverage AOT compilation for high performance and static analysis etc. A language with the learning curve of Python, near C performance and strong safety guarantees of Rust. There is nothing that suggests to me such a language would not be possible. Swift and C# come closest but they are warped by their respective corporate overlords.
I agree about Swift fitting this mold, it's unfortunate that it has failed to gain traction outside of Apple's ecosystem.
C++ also does "lie" about reality in a lot of ways, in the sense that the actual compiled program behaves quite a bit differently than a plain reading of the code implies to a nonexpert. The most obvious form of this is the rules for effect ordering. Even the recurring complaints about the compiler doing unexpected things based on the as-if rule and undefined behavior seem to count here.
"C++ Initialization Story: A Guide Through All Initialization Options and Related C++ Areas"
https://www.amazon.com/Initialization-Story-Through-Options-...
"Undefined behavior" is not reality; it's a strange shadow world where anything may happen. The compiler reserves the right to reject your reality and substitute its own - which it will not show you within the language, nor let you choose or hint, nor warn you.
People like to use C and C++ as if they were a species of typesafe macro assembler that always produces predictable machine code, and that breaks down often enough that the world is full of systems-level CVEs.
Most real world security issues have nothing to do with code. Social engineering breaks more security than memory issues. We are also skewed by many of the fundamental programs are written in C and so there is a lot of value in finding holes.
I'd wonder whether that's just me being dim, but if so then I think Bjarne Stroustrup and Herb Sutter are dim in the same way. (Stroustrup, quoted approvingly by Sutter: "Inside C++, there is a much smaller and cleaner language struggling to get out." and "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization.") And it doesn't seem likely to me that they are ignorantly overpessimistic about the complexity of C++.
To surprise of many outside of .NET community, it has much better performance than Swift[0][1][2] and lets you match performance of C and C++ in areas that matter.
[0] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
[1] https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
[2] https://benchmarksgame-team.pages.debian.net/benchmarksgame/... (better performance for short-running benchmarks sensitive to JIT)
(no direct comparison but Tl;Dr: ARC and Swift defaulting to virtual dispatch that can't always be elided with WMO results in Swift having much lower (even if more consistent) performance)
Also wow, I stand corrected, Swift is a much stronger contender in Benchmarks Game scenarios than earlier microbenchmarks and research projects that I've seen would suggest. Particularly when using its own SIMD abstraction, it is likely LLVM makes very competent transformations, possibly merging narrower operations into a wider ones, replacing one set of intrinsics with another, etc.
However, still seems to suffer quite a bit, just like Go, in Binary Trees (it's not like C# does the best there either, the winners use bump allocators, and then there's also a 2.6s magic entry by Java, write barrier elision?).
And while Android ART technically isn't Java (the plaform), its mixed execution mode with hand written Assembly interpreter, JIT, AOT with PGO shared via play store is quite cool.
Sounds like Go or Zig, though Go is slightly less safe and slower - in turn being much easier.
Though I'd reach for Go first if I'm working with anything concurrent. Concurrency is hard, and Go really helps with getting it right.
USB Armory is still being sold.
Sure, you can compile and host your .NET Core web app on Linux these days, but desktop applications are a completely different beast. You're often lucky if one exists at all, and if it does there's a decent chance it's not designed for "modern" C#-on-Linux. It really isn't a viable option yet, maybe in a decade or two.
https://github.com/dotnet/wpf/issues/2554
That said, there are cross platform, open source .NET UI frameworks out there, including one that is inspired by WPF:
Sometimes classes aren’t the abstraction I want to use.
That’s why I like kotlin over Java or c#. It’s be nice to have a language like that which targets .net
They have support for these features, but they require some ceremony to use (like wrapping function arguments in a Callable), which increases friction, signal to noise ratio, and decreases the expressiveness that I want.
I just want to write what I want without having to think too much about how the language wants me to write it. Go is good for that. JS too, but has a lot of historical cruft.
var foo = [1,2,3];
Add on switch expressions, primary ctors, first class functions, no ceremony in Program.cs anymore... it's hard to get more terse and expressive unless you want lots of chained single symbol operators (look at F#).
void myFunc(List<int>){}
myFunc([1,2,3]);
The expression sytanx can infer the type from the caller syntax so you can just inline the array declaration.
The downside of all this is that the only people that know how awesome C# is are people who are already doing C#. Growing the pot and trying to convince people to give it a go in its modern (post dotnet 5/"core") iteration is like pulling teeth. Everyone assumes nothing has changed since like C# 2, and given the fact it's a "boring" language that doesn't have Hacker Hype behind it, people just ignore it.
Every week you have people on HN wishing for something that does exactly what C# does but don't want to give it a shot or admit that C# is _actually_ an incredible language and toolset that does exactly what they want (and more).
https://learn.microsoft.com/en-us/dotnet/api/system.reflecti...
https://github.com/dotnet/runtime/blob/main/docs/design/feat...
https://news.microsoft.com/2001/10/22/massive-industry-and-d...
Anyone with the pre-release MSDN .NET SDK that was initially made available to MSFT Partners, with alpha documentation written in red, has a folder with tons of functional languages that could take advantage of tail calls.
F# only came later into the picture, even considering its OCaml for .NET origin.
As an aside, and as someone who's currently going through materials such as Crafting Interpreters (and who knows what else based on suggestions from r/compilers), is there a, I guess, guide for people who want to implement a compiler that targets the CLR/CLI?
I have a copy of CLR via C# 4th edition, but other than that not sure what else I can reference targeting anything newer than .NET Framework 4
This might be a bit of a learning curve if you don't have experience with writing compilers like that though.
There are other materials that might prove useful should you go down this path:
- A series of blog posts of a student who is writing a Rust to .NET compiler which you could follow along: https://fractalfir.github.io/generated_html/home.html The project itself: https://github.com/FractalFir/rustc_codegen_clr
- A video series on writing a compiler for .NET from scratch: https://www.youtube.com/watch?v=wgHIkdUQbp0&list=PLRAdsfhKI4... Notes: https://github.com/terrajobst/minsk/tree/master/docs
As for CLR via C#, while it has certain facts that remained the same, the ecosystem has changed a lot since then, you really want to target the latest LTS to get best experience and performance. If you liked it, then I can strongly suggest https://github.com/dotnet/runtime/tree/main/docs/design/core... which covers .NET runtime internals.
p.s.: if it's any consolation, the promise in CLR via C# of JIT having the advantage of dynamically profiling code execution and compiling to profile-guided version, tuned to the exact hardware and environment is finally fulfilled, many years later :)
Also, JVM ecosystem does not offer the ability to provide zero-cost abstractions, which C# does with monomorphized generics. This is a hard requirement for productive systems programming if you don't want the C level of verbosity.
This is true, wasn’t thinking about that.
C# always requires me to use and organize my code into classes and my data into objects.
For example, If I wanted to use higher order functions, I need to wrap functions in callables (Objects).
If I wanted to throw together a quick script to test, I need to set up a program which uses some magic configurations so that I don’t need that entry point class.
Granted I only worked with C# professionally for a couple of years before getting back to a Go shop, but it always felt like trying to avoid thinking in objects was fighting against the language.
Personally, I like just having first class functions which take simple data structures as arguments. Modeling the world as objects just isn’t as clear to me (the old OOP vs FP discussions)
Languages like C# and Java turn me off for that reason.
I used C# for a long time, and when I started using JS, I marveled at the directness of its expressiveness, its lack of ceremony.
What's actually needed is a language as direct as JS, with the bad parts stripped out, types and a proper standard lib added, and compiled to byte-code for performance.
They can call it a different name if they want.
It’s important for how I like to code.
It has always had a fair share of FP features and only gained more as it kept evolving. It had higher order functions partially in the form of method group to delegates conversions since C# 2.0, released 19 years ago and to a full extent in the form of lambda expressions since C# 3.0, which also included LINQ, released 17 years ago. It is a mixed-paradigm language.
On the off chance that this is trolling, I must point out that Java and C# are sufficiently distinct, with the latter leaning more heavily on offering both lower and higher level features like extensive pattern matching, LINQ, structs and pointers/byrefs.
If you do have C# snippets you are unhappy with - please post them. There is likely a better way.
(I noticed you posted about Callables twice, and had a quick search - it appears to be a Godot-specific abstraction imposed on C# due to inadequacy of GDScript it has to interoperate with, and has nothing to do with C# itself)
In one way, they help the adoption in the games industry, in the other hand they introduce so many anti-patterns with their reflection based SDKs, and use of magical methods instead of proper .NET code.
Regardless of the actual word, there is an extra thing I need to do to pass a function to a higher order function as an argument.
I was using this as an example to demonstrate how C#’s support for the language features I like require some ceremony which reduce expressiveness and require me to think about the language more.
Post an actual example (code).
https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
I need to create a delegate for any function I want to pass around.
var func1 = (int i) => i + i;
var func2 = () => func1(1) * 2;
Console.WriteLine(func1(4) + func2());I would also place D and Swift into that bucket, only D never managed to really take off when it had the opportunity, and Swift is clearly at home in iDevices.
Any evidence for this? From the benchmark game it seems like c# is more performant than go, and measurements are done on Linux.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Which? Just curious.
Some-of the spectral-norm programs seem quite similar:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Floodgates will open though once class is also supported.
The first thing that crossed my mind reading this comment was Nim. It’s pretty Pythonesque (but that’s kinda what I hate about it.)
Also channels and WaitGroups are easy to misuse (well, that's why there's also an ErrGroup now).
I'd make Golang my 100% go-to language if it got rid of several footguns.
Though let's be real, that's absolutely certain to never happen, they take their backwards compatibility extremely seriously. So we'll have to keep relying on checkers and linters that alleviate some of the pains somewhat.
In fact, most projects I've worked on won't even merge a PR with ignored errors.
Also, `go test -race` is a Go feature, there's nothing wrong with using it.
But it's worth remembering that it's a dynamic analysis that only covers access patterns created by your tests.
Lets not forget that C# only exists because Sun won the lawsuit over J++, where several C# features were born (J/Direct => P/Invoke, events, JFC => Windows Forms, COM support), and Ext-VOS (the COM next generation runtime) paper refers to J++, before COOL became C#, and J# was introduced as migration path from J++.
Ironically, after all these years, as means to keep Azure relevant for the Java community, Microsoft has become a OpenJDK contributor by acquiring jClarity, including ARM support on Windows, better escape analysis, and has their own distribution.
And VSCode plugins for Java, developed in collaboration with Red-Hat, are much better than C# Dev Kit, without requiring a Microsoft account.
The description is confusing though. I suppose they did the whole "rebrand" thing because for years everyone thought poorly of experience of writing C# in VSC while it has always been semi-decent (depending on your skills) and got better recently, especially after moving to Roslyn-based LSP from Omnisharp.
This allows for a spectrum of JVM implementations, where basically any CPU capable of running some form of Java implementation has a JDK available.
https://en.wikipedia.org/wiki/List_of_Java_virtual_machines
Likewise, most frameworks are driven by set of companies, based on industry standards, where .NET due to its culture, most Microsoft shops tend to only adopt what comes from Microsoft.
It is a long discussion problem in the .NET community that when many FOSS projects finally manage to get enterprise adoption, Microsoft ends up coming with their own solution, which said enterprises then switch to, thus killing those FOSS efforts.
Listen to big podcasts like .NET Rocks, where this tends to be often discussed with their guests.
Does Microsoft somehow prevent anyone from developing a CLR implementation for CPUs they don't support? The only reason the JVM is everywhere is because they had first-mover advantage.
How does Microsoft developing their own solution to a problem "kill" FOSS efforts, exactly? Microsoft's solutions are almost all also FOSS. Is competition some kind of inherent problem? Or is a large corporation creating and maintaining FOSS a problem? Why aren't you annoyed at those enterprises that started adopting the original FOSS projects, but didn't contribute dev time to maintaining them? Instead you're annoyed at Microsoft for creating its own FOSS and paying to maintain it for the community for free.
I just find most objections to .NET so bizarre, it's almost always knee-jerk Microsoft hate.
.NET virtual machine, MIT license: https://github.com/dotnet/runtime
SDK, MIT license: https://github.com/dotnet/sdk
You were saying?
No, the point is, who cares as long as everyone is happy with the direction?
Now granted, I don't know if it would be different with Java and Oracle. But that is the point. A significant enough change proposal for C#, supposed like above, a change proposal that goes against where Microsoft thinks the development path of C# should be, that is the important bit. So long as contributions and feature request align with Microsoft's interest, its all good, but if the community has large support for a contribution or change request that doesn't align, then it may be different.
Addition: Although arguably, this is true for most languages. Even FOSS languages have some type of leadership that those big shifting proposals will live and die with. Not intending to cast anyone in a bad light, but just a project leader and a project I am familiar with. If a proposal that was as ground shifting as the proposed above was suggested for Zig, whether or not the project does it, the decision of whether to do it may live or die depending on Andrew Kelly or his close knit team of contributors to Zig. Same could be said about Rust, the project has leadership and they could kill a popular proposal. Even outside of languages. The linux kernel, a feature or restructure may live or die by the hand of Linus Torvalds.
Speculation about what Microsoft may do or may not do in response to community pressure is a weird kind of criticism. I can invent all sorts of fictional boogeymen too. What if Linus has a psychotic break and wants to make the Linux syscall interface mimic the Windows NT kernel?
People would do exactly what I'm suggesting here: fork and continue development without him.
As you also say, leadership means leading, and Microsoft takes backwards compatibility very seriously, and that's why people trust them and use their offerings, sometimes instead of open source offerings.
A change like that would invalidate every tutorial and document ever written about C# over 25 years, so is it really C# anymore? I don't think anyone would make that change. It's simple to fork the MIT licensed C# compiler, call it C* and make that transition in a language that is definitely no longer C#. I just don't think this is a good example.
For good examples, see the language evolution that has actually taken place which has been community driven in many cases. Records, tuples, the evolution of type constraints have all been started from community feedback, and are backwards compatible.
As a final note, a comment on "aligning with Microsoft's interests". They are interested in two things for .NET: selling Azure hosting, and incentivizing Windows programming. Making the software development experience pleasant, robust, predictable and backwards compatible are the objectives, so devs are incentivized to pay them to program on their OS, target your program to their OS, or run your software on their cloud infra. I really don't see the problem.
https://github.com/dotnet/csharplang/blob/main/meetings/2024...
https://github.com/dotnet/csharplang/blob/main/meetings/2024...
I find such hate quite understandable. I might not share it, but I would not put any trust in MICROS~1 either, especially not in a professional setting.
When I develop applications for MICROS~1 platforms, I do it in Java, betting that the Eclipse foundation and a plethora of corporations, including MICROS~1, aren't going to suddenly team up and do something nasty to the language and important libraries. With MICROS~1 as the lone manager of a language and most of its 'ecosystem' this seems like a real risk.
Why? All of the code is MIT licensed. If MS does anything untoward, forking it is trivial.
Look, there are obvious differences in how Java and the JVM are maintained and evolved as compared to .NET, but to continue claiming that these differences really matter all that much is just FUD, to put it politely.
No, it's not. Is Mono independent today?
The only reason I’d use or contribute to Oracle I.P. is if I was paid to do so. The other is porting off Java but that’s legally risky if minima changes. CPAChecker was the one I was thinking of. Past that, I’d try to avoid Java.
The question is: is it harder than it needs to be due to accidental complexity[1] in programming language design. TFA argues that there is still some accidental complexity in the design of Rust that leaves room for an "easier" language that still allows the level of control needed for a systems language.
[1] https://medium.com/@sharkaroo/navigating-complexity-in-softw...
- Strip out the bad parts.
- Add types, more functional features, a proper standard lib, and a standardized module system.
- Allow compilation directly to performant machine or byte code.
They can call it a different name if they want.
- It has simple, straightforward, and pleasant syntax/semantics. Except for a few tweaks here and there, there is no need to re-invent the wheel here.
- It is pretty functional, all that's needed is to add everything-is-an-expression
- It supports multiple paradigms (functional, imperative, OOP)
- Since it has no types currently, a _modern_ type system can be feasibly added, supporting concepts like result/error instead of exceptions, maybe/option, pattern matching, etc.
- It has easy-to-reason about async features
- It has a huge ecosystem; it shouldn't be hard to port many high-quality packages to a similar language
Now, the lower-level features are another matter entirely, but it shouldn't be too difficult to retrofit what we want, like multi-threading (maybe building on top of workers), utf-8 strings, and so on.
...Spoken like a person who's never coded anything meaningful or serious, if you can't even gauge the complexity and man-hours needed to create (much less port) high-quality packages between programming languages.
Talking about "man-hours" tells me you think of software engineering like an assembly line, which it is not.
There are whole lot of factors that might influence the time needed for such porting; the keyword in my comment is "similar". If the new language is a superset of the original language, minus a few warts, I'm not sure what should take so much time. For one thing, the architectural design of the packages (which probably took most of the original effort) remains the same.
The funny thing is that MACLISP could do this back in the early 70s. While Lisp is a dynamically typed, GCed language, MACLISP let you annotate variables to promise that they would only be called on certain primitive types, thus supporting compile-time optimizations for numeric code and not necessarily allocate memory, follow a pointer, unbox data etc. This was critical to make packages like MACSYMA run on machines with performance in the KIPS range.
Although languages like Python support similar declarations or annotations, they aren't used in the same way -- if you want performance you write the module in C++ and give it a python interface.
And woe betide you if you happened to violate the preconditions of one of those annotations. That kind of programming is no better than C with "Segmentation fault. Core dumped."
The problem is that "system programming" or "embedded programming" isn't one thing.
There are people who want to throw around a zillion polygons at 144FPS. There are people who want max performance on x86-64 shoveling around millions of network connections. There are people who need guaranteed real time response or someone dies. There are people who need to stay asleep all the time to conserve battery and only wake up once every 5 minutes to chirp some RF.
These people do not have the same needs in a language yet they almost all wind up shoveled into C.
Umm, this is MACLISP we're talking about -- you know the types at runtime and can pass the unspecialized argument to an ordinary function. A kind of generic function I suppose, though we didn't use that vocabulary back then.
Just because this was 50 years ago doesn't mean that programmers were morons.
Consequently, whatever you sent in was assumed to be what the annotation was. There was no "compilation phase" to ensure that the type at the call point actually matched the type in the annotation. If those differed, then your program would just go boom.
Now, it could be that I'm remembering something other than MACLISP. It's been a lot of years.
I tried to be clear by using the modern terminology “generic function”.
If you compile a function with those annotations you’d end up with two functions: one that could be called if you knew the types at compile time and one that did not.
Why is that tho? There are better languages for each of those applications. What do you think, is it business requirements? Hardware? Regulation? Legacy code?
C is a great choice because I can hire people who know C and get good results. A domain specific language will not be much better than C for any of the above. It turns out that all the hard problems listed above are similar enough that someone good in one will be good in them all and so I can hire C experts from any field and get going fast without having to teach them my domain specific language that isn't really much better.
I do not know the best way for any modern language to take to any other modern language - but 1972 C is a big negative to them all.
On other systems we have XPC, COM, AIDL, TIMI, MSIL.
That said I fully accept your correction. (even though it doesn't apply in my world)
What's really lacking are safe, easy, strong typed general purpose languages that can leverage AOT compilation for high performance and static analysis etc. A language with the learning curve of Python, near C performance and strong safety guarantees of Rust. There is nothing that suggests to me such a language would not be possible. Swift and C# come closest but they are warped by their respective corporate overlords.