Holding grudges against programming languages

As a programmer, I come in contact with various technologies and develop strong opinions as I work with them.

As there are many stereotypes surrounding these languages, I will try to avoid picking the low-hanging fruit, such as “lol C segmentation fault”, and instead focus on parts that are more specific to that concrete language, or their standard library.

I will avoid discussing the following topics:


I am of the opinion C should stay rather thin and avoid introducing complexities. One of the things I hate about C is macros, but I will not hold that against the language.

Charsets are hard

I dislike C locales, they add unnecessary complexity to a systems programming language and cause a large amount of issues. This goes hand in hand with another problematic feature — strings. Yes, I get it, it's very easy to call this bad design in hindsight, but I feel like any new code should avoid monstrosities like wchar_t. UTF-8 is your friend, just don't misuse strlen.

The standard library feels... arbitrary

Many parts of the standard library like <random.h>, <time.h>, or some string operations (strtok), are outclassed by basically any other solution or get written again anyway. This is partially fueled by my dislike for hidden thread-local state. Obviously it would not make sense to remove them (mostly for beginners' sake), however I would argue C might be better off with a thinner STL.


C++ is without a doubt the most complex programming language in the world with its parsing ambiguities, scope creep, or even just the amount of keywords. On the other hand, C++'s designers are very well aware of the threat named Rust and silently add competing features while somewhat dismissing potential memory safety features. I'll be nice and spare you a dependency management rant.

Implicit behavior

This is often a disaster waiting to happen. Return value optimization, move semantics, overloaded operator=, move constructors. There is a non-zero chance your first attempt at moving a non-POD object will lead to a double free. Even standard operator overloading (cursed examples being everyone's favorite iostream and std::filesystem::operator/) can lead to bad surprises, let alone the move assignment operator. Memory semantics should be explicit and clear without any “implementation defined” trickery.


Both std::span and std::string_view arrived rather late and in turn basically don't exist in production code. Depending on the age of the code, they will accept a const-reference to an std::string or an std::vector, maybe an iterator, or worse, a raw pointer and a size parameter.

Functional programming

Functional programming in C++ is simply terrible. The <algorithm>, <functional> and <numeric> API is intimidating and unreasonably convoluted for any slightly complex functional programming pipeline. The ranges API seems to attempt to fix function chaining (with more operator overloading, yay), however I didn't have the opportunity to try them yet.


C# is a very nice language, however I feel like it has somewhat of an identity crisis and attempts to mimic the current hot thing of the month, which leads to feature creep. I lost track at roughly C# 7.0. As much as I dislike the dual LINQ syntax, I overall think LINQ itself is a very good feature.

Asynchronous programming

I am not qualified enough to talk about this, but async programming in C# seems very chaotic and difficult to keep track of the asynchronous context. It's one of the easiest languages to use async wrong, especially in combination with threads and locks. I've heard horror stories of someone's code mysteriously freezing upon using async, for example pausing a thread shared with a scheduled awaited task could lead to a deadlock. I also prefer to choose my async task executor, and .NET makes that rather clunky.


Java is a surprisingly fine language... is what I would say if it wasn't 5 years late to the party. It correctly identified the trends in programming language design, however these features may not be accessible for another few years. Unicode handling is just okay. There are ways to work with codepoints, however the API is a bit lacking.

Value types

The distinction between primitives and reference types has been a major pain point of Java basically since Java 5, when generics were added. Project Valhalla will introduce proper value objects and allow generics over primitives.

Pattern matching

While this oversight is being actively worked on, this is an absolute necessity to make records and sealed/final classes worthwhile. Switch is the strongest control flow statement and Java needs better pattern matching to make it more usable.

// An example of proposed pattern matching in switch, combined with instanceof patterns
switch (obj) {
    case String s  -> System.out.println("string: " + s);
    case Integer i -> System.out.println("int: " + i);
    default        -> System.out.println("default");


Java doesn't have async (which is fine), however OpenJDK has no alternatives without using libraries or kernel threads. Project Loom will rectify this issue with lightweight threads on the virtual machine level, however Java still deserves some sort of channel feature similar to oneshot, mpsc or Go channels. Going the boilerplate way of using blocking queues is very tedious.


Rust is a rather well-designed programming language, however nothing is perfect. Since it does not have classes, Rust struggles at some abstract concepts from object-oriented languages, like deep type hierarchies, but there are generally ways around that.

File systems

Unlike the amazing Java NIO FileSystem API, Rust's filesystem API is hardwired into the standard library and impossible to write a custom external implementation for. I understand that it's caused by the underlying architecture, but it unfortunately limits my ability to create something like JimFS or ZipFileSystem. On the other hand, I am grateful for the distinction between strings and operating system-native strings encoding-wise.

Procedural macros

Several libraries I've used featured some sort of procedural macro, which is fine, when they are documented. Without proper documentation, procedural macros are entirely opaque and the user has absolutely no idea what's going on internally. This is a very frustrating issue, especially in an ecosystem like Cargo, where projects pop up very fast.


This is a personal one. Tunnel-visioned on writing efficient code, e.g. RAII, can be tempting to write, however using the wrong design paradigm can quickly lead to very stiff code that is impossible to untangle. Keep things simple and don't be afraid to use smart pointers when it can reasonably eliminate lifetimes. Most of your code doesn't run in a hot loop anyway.


PHP has managed to pull off an impressive amount of work towards fixing the language in the recent years, however the piled-up terrible legacy design is impossible to uproot now. I'm not sure if I will ever return to PHP.


I don't even know where to start with this one. Python is deceivingly easy to write, and quickly becomes a nightmare for any project that is more than three source files.

Keyword parameters

I've never hated any API more than matplotlib's keyword arguments. I find myself constantly juggling four tabs of documentation desperately trying to make sense of the functions. Some use internal state, some use parameters. Without having Firefox open, I would have absolutely no idea how a single function works, let alone the entire library. Keyword parameters are overused in this regard. Seaborn makes it slightly more tolerable, but Python plotting will probably always stay like this for me.

# This is one of the less cursed functions out there
seaborn.lineplot(data=None, *, x=None, y=None, hue=None, size=None,
    style=None, units=None, palette=None, hue_order=None,
    hue_norm=None, sizes=None, size_order=None, size_norm=None,
    dashes=True, markers=None, style_order=None, estimator='mean',
    errorbar=('ci', 95), n_boot=1000, seed=None, orient='x', sort=True,
    err_style='band', err_kws=None, legend='auto', ci='deprecated',
    ax=None, **kwargs)


Thanks to duck-typing and some other dynamic language tricks, there is no hope determining the type of your object. Your best bet is using a step-by-step Jupyter notebook with a decent IDE to try to describe the shape of an object. These very dynamic objects make type hints difficult to use.


Using a carefully chosen subset of JavaScript can make writing code much nicer, however it is still very difficult to statically type check, if at all possible. This can especially be felt when handling a web page's DOM (Document Object Model). JSDoc type hints partially alleviate this issue, however at that point it is easier to simply use TypeScript. Please do not abuse the dynamic nature of JavaScript, it will come back to haunt you.


It's very difficult to pick something against TypeScript. Most complaints I would have against it could be pointed against the JavaScript ecosystem as a whole. I think my only complaint would be enums, or rather the lack of enum classes or a similar feature. Using strings as enumeration values is a code smell, but this is one of the issues TypeScript inherited from JavaScript.


No language is perfect and will never be. Personally I like Rust and TypeScript for their powerful type systems, however something like Python is still faster for one-offs like BeautifulSoup or GraphViz. That being said, I would appreciate some of the modern programming language syntax innovations in many of the aforementioned languages.

— Natty 🏳️‍⚧️

Tags: #rant #programming