PONYλM2Modula-2
CodeCompared
for C++ programmers

You already know C++.Now explore other languages.

Side-by-side, interactive cheatsheets for C++ programmers
comparing C++ to other languages. Every example runs live in your browser — no setup, no installation.

▶ Start with RubyBrowse comparisons ↓Explore the language map ↗

Choose your own path by reordering languages

Ruby⚡ Works Offline⚡ Offline

Ruby's C API is one of the most-used C APIs in the world, and that is why you are here. Every gem that is fast is a C or C++ extension — nokogiri is libxml2, pg is libpq, sqlite3 is SQLite — so the common path is not "C++ developer learns Ruby" but "C++ developer writes the fast part of somebody's Ruby program". Four lines of registration and an extconf.rb, and the call site cannot tell.

  • The extension API is small and has barely changed in twenty years: VALUE is the universal object type, Init_<name> must be extern "C" or mangling hides it, and Ruby checks arity before your function runs
  • rb_thread_call_without_gvl is what makes an extension actually parallel rather than merely faster — and touching any Ruby API while the lock is released is a crash
  • mruby is Ruby in the Lua slot: a few hundred KB, no global interpreter state, several independent mrb_states per process — where CRuby is what you extend, mruby is what you embed
  • The block is the construct C++ has no word for, and it replaces RAII: a method brackets the block it is given with an ensure, which is how every resource in the standard library is handed out
  • Open classes let you reopen String itself — globally and retroactively, which is both how Rails works and how two gems break each other
  • No destructors and no usable finalizer, so anything with a lifetime should be handed out through a block-taking method rather than trusted to a caller
GoPre-Alpha

The exit taken by people who are tired of the build, not of the language. Go was designed at a company where a C++ build took the better part of an hour, and almost every omission on this page is downstream of fixing that. A package compiles once and exports a summary, so importing costs the size of an API rather than the size of a codebase — and that one decision is why there are no headers, no cycles, and why generics took twelve years to arrive.

  • RAII becomes defer, and the obligation moves — a destructor belongs to the type, so every user gets cleanup free; a defer belongs to the call site, so every caller must remember it and the compiler will not remind them
  • &localVariable returned from a function is idiomatic, not a dangling pointer: escape analysis decides stack or heap for you, so the allocation choice you have made your whole career is simply not yours here
  • Interfaces are satisfied structurally, which inverts a dependency you have always paid — the consumer declares the one-method interface it needs and a third-party type satisfies it without ever having heard of you
  • Goroutines cost about two kilobytes against an OS thread's eight megabytes, so one-per-connection is a normal design rather than a mistake — and the race detector ships in the toolchain as one flag
  • No const-correctness at all, no operator overloading, no inheritance, no exceptions, and generics that only avoid writing a function twice — every one argued for rather than merely unimplemented
  • One binary, statically linked, cross-compiled with two environment variables — which is most of why Docker, Kubernetes, Terraform and Prometheus are all Go programs
PythonBeta⚡ Works Offline⚡ Offline

The language you will end up writing anyway — for the bindings, the build tooling, the data work. Almost every Python surprise a C++ programmer hits comes from one fact: a name is a reference to an object, so assignment never copies, a scope never ends a lifetime, and a signature is never checked. Learn the object model first and the rest of the language stops being surprising.

  • second = first does not copy — there is no value semantics anywhere in the language and no way to ask for it, which is also why a mutable default argument is shared by every call that omits it
  • RAII becomes the with statement, which is a genuinely good answer — but it guards a block, not an object, so it does not compose through data members the way a destructor does; __del__ is a safety net and never a cleanup mechanism
  • Duck typing is a template without the compile step: both accept any type that structurally supports the operations used, and the only difference is whether the check lands at instantiation or at the moment the line runs
  • Keyword arguments are the feature you will miss going back — and a bare * in the signature makes the trailing booleans keyword-only, turning the classic unreadable call site into a TypeError
  • Expect one to two orders of magnitude on an interpreted loop, so the rule is not to avoid Python but to keep the inner loop out of it — which is exactly why numpy and every scientific library look the way they do
  • The GIL will invalidate your first design: threads give concurrency but not CPU parallelism, so four compute-bound threads take as long as one — reach for multiprocessing, released C code, or the free-threaded build
CPre-Alpha

The subtractive direction, and the one where you already know the syntax. Almost nothing on this page needs teaching from scratch — what needs naming is the list of things you will reach for out of habit and not find. No destructors, so lifetime becomes a discipline; no overloading, because one name is one symbol; no templates, no containers, no exceptions. Then the shorter and more surprising list: the things C has that C++ never adopted.

  • RAII is the big one, and its replacement is goto cleanup — not a code smell in C but the accepted answer, used throughout the Linux kernel, because duplicating the free list at every early return is how you eventually miss one
  • One symbol per name is the single constraint behind several absences at once: no overloading, no default arguments, no namespaces, no member functions — and it is also exactly why every other language can call C
  • private becomes the opaque pointer, which hides fields from the compiler rather than just the programmer — stronger encapsulation than C++ offers, at the cost of never being stack-allocatable by the caller
  • virtual becomes a struct of function pointers you write out by hand, which is the clearest explanation of what a vtable actually is that you will ever read
  • C is not a subset of C++, and the differences are quiet ones: sizeof('a') is 4 here and 1 there, union type punning is defined in C and undefined in C++, and const int is a variable rather than a constant expression
  • Genuinely C-only, and worth knowing even if you never leave C++: variable-length arrays, restrict, compound literals with real addresses, flexible array members, and designated initializers in any order
KotlinPre-Alpha

The one language here you will not choose — you will end up next to it. A C++ developer meets Kotlin at the Android NDK boundary, where the engine, the codec or the shared cross-platform core is yours and everything above it is not. So this page is about where your C++ intuitions stop applying in the layer above your .so, and it ends on JNI, which is the part you will actually work on.

  • There are no value types for your own classes — every instance is on the heap, every variable is a reference, and second = first aliases rather than copies; data class plus copy() is the closest thing back
  • Int compiles to two different things: a machine int as a local or in an IntArray, and a boxed heap object inside a List<Int> — perhaps twenty times the memory of a std::vector<int>
  • Generics are ERASED, so List<Int> and List<String> are one type at runtime, there is no specialization, and reified only rescues it inside inline functions
  • Null safety is genuinely enforced rather than warned about — a String cannot be null and a String? cannot be used unchecked, both compile errors, with smart casts so no cast is written
  • No destructors: use blocks carry cleanup, and finalizers are deprecated and never the answer — the same trade Go, Zig and C# make
  • JNI is the least pleasant part of both languages, and a mismatched signature is an UnsatisfiedLinkError in production rather than a compile error — but Kotlin/Native compiles through LLVM with real C interop, which is a better boundary when you can use it
RustPre-Alpha

Everything you already do by discipline, checked by the compiler instead. RAII is not an alternative to Rust's model — it is Rust's model, extended until the compiler can prove it. Destructors, move semantics, templates, smart pointers and const-correctness all arrive under new names; what is genuinely new is that use-after-move, dangling references, iterator invalidation and data races stop being bugs you avoid and become programs that do not compile.

  • std::move disappears, because assignment already moves — and the moved-from value is not "valid but unspecified", it is statically poisoned, so use-after-move is a compile error rather than a state you have to remember not to read
  • unique_ptrBox, shared_ptrRc or Arc (Rust splits the atomic refcount from the cheap one, so single-threaded code stops paying for thread safety it never uses), and neither can be null — absence is Option
  • The rule of five collapses into one derive line: a type gets no copy, no comparison and no printing until you name what you want, and Copy is only offered where a bitwise copy is actually correct
  • Templates monomorphize exactly as they do in C++, but the body is type-checked once against its trait bounds rather than at each instantiation — so a mistake points at your definition instead of unrolling into the standard library
  • std::variant + std::visit becomes an ordinary enum with named variants and exhaustive match; add a case and every incomplete match in the codebase stops compiling, which is the check -Wswitch gives up on the moment a default: appears
  • No exceptions, no inheritance, no preprocessor, and no undefined behavior outside unsafe — which is a five-item list, not an off switch, so auditing memory safety means auditing the blocks that are labeled
SwiftPre-Alpha

The one language that can call your C++ classes without a shim. Swift 5.9 shipped direct C++ interoperability — importing headers, methods, templates and operators as a module — which is a different proposition from Rust, Go or C#, all of which meet C++ across a C-shaped boundary. The spine of the language is value semantics: struct copies as it does in C++, and copy-on-write makes that cheap enough to build a whole standard library on.

  • Copy-on-write removes the reason you write const& everywhere — passing an array by value costs a retain, not a memcpy, and duplicates only when someone writes
  • ARC is shared_ptr with the retains inserted for you: deterministic deinit, no collector, no pauses — and the same retain-cycle problem, solved the same way with weak
  • ~Copyable closes the use-after-move hole: a consumed value is a compile error, not a legal read of a "valid but unspecified" state you had to remember to leave sensible
  • some versus any spells the static/dynamic dispatch choice in one keyword position, where C++ needs a template and a base-class pointer
  • Protocol extensions give shared behavior without shared state, and retroactive conformance makes a type you did not write satisfy a protocol you did
  • Swift 6 checks data races at compile time via actors and Sendable — but one vendor sets the roadmap, and off Apple platforms the ecosystem is thin
ZigPre-Alpha

The successor candidate that is arguing with C++ directly. Zig's thesis is one testable question: reading a single line, can you tell whether it allocates and whether it can jump somewhere else? In C++ widgets.push_back(w) may allocate twice, move every element, run constructors and destructors, and throw — none of it visible. In Zig the allocator is a parameter, the failure path is marked try, and cleanup is a defer you wrote.

  • comptime replaces the preprocessor, templates, constexpr, consteval, if constexpr and static_assert with one mechanismtype is an ordinary value, so a generic type is a function that returns a struct and reflection is a comptime value you loop over
  • There is no global allocator, so a function's signature tells you whether it can allocate — which makes arena and fixed-buffer allocation ordinary rather than heroic
  • ReleaseSafe has no C++ counterpart: fully optimized and still trapping on out-of-bounds access, integer overflow and bad casts, so an exploitable memory error becomes a crash with a stack trace for a few percent
  • errdefer is the piece C++ lacks — the "undo what I have done so far" half of constructor exception safety, written out instead of emerging from destructor ordering
  • zig cc is a drop-in C and C++ compiler that cross-compiles anywhere from anywhere, libc included — worth adopting even if you never write a line of Zig, and how most C++ programmers meet the project
  • The costs are real: no destructors, no operator overloading, no closures, no inheritance, no ownership in the type system — and it is 0.16, with breaking changes in every release and no standard
C#Pre-Alpha

You already know the syntax, and that is the trap. C# kept C++'s braces, semicolons, operators and control flow almost unchanged and replaced the machine underneath. The one thing to internalize first is that class and struct are not near-synonyms here — that single distinction decides copy semantics, storage, nullability and equality, and assignment, null, boxing, ref and Span<T> are all downstream of it.

  • struct copies, class aliases — the same two keywords you read as interchangeable now decide whether b = a duplicates the object or gives it a second name
  • RAII becomes using and IDisposable: the guarantee survives, but the obligation moves from the type to every caller, and a finalizer is almost always the wrong tool
  • Boxing is the one trap with no C++ analogue — a value type crossing into object allocates silently, which is what dominates C# performance advice in game code
  • Generics are reified rather than erased, so List<int> really stores ints — but no specialization, no non-type parameters, and the body may only use what the constraint proves
  • Span<T> is std::span with the lifetime rule enforced: the compiler refuses to let it be boxed, stored in a field or captured, so the dangling-span bug does not compile
  • async/await shipped in 2012 with the whole stack — state machine, Task, scheduler, library — where C++20 gives you co_await and leaves the rest to asio or cppcoro
LuaPre-Alpha⚡ Works Offline⚡ Offline

Lua is not an alternative to C++ — it is already inside your program. Every game engine, Redis, nginx, Neovim, Wireshark. So this page is about the BOUNDARY rather than the syntax: how your classes get bound, who owns an object once Lua can see it, and the one interaction that silently skips your destructors. C++ owns the frame budget; Lua owns the decisions.

  • sol2 makes the virtual stack disappearlua["damage"] assigned to an int generates the push/pop/convert you would otherwise write by hand and get wrong, and it binds a class with its methods in one call
  • Handing Lua a pointer lends the object, a value copies it, and a shared_ptr shares it — the syntax is identical for all three, and a raw pointer outliving its Lua reference is the classic embedding crash
  • lua_error is longjmp, and longjmp does not run C++ destructors — build Lua as C++ so errors unwind properly, and never let a C++ exception escape into Lua
  • RAII still works on objects Lua owns, but the collector decides when: leaving scope makes a value unreachable, not destroyed, which is fine for data and a slow leak for a file handle
  • One data structure for everything (the table), indexed from 1, where 0 and "" are both TRUTHY and an undeclared name is a silent global
  • The cost is the boundary, not the interpreter — cross rarely with a lot of work, and check whether your engine embeds PUC Lua 5.4 or LuaJIT, which is a 5.1 dialect with an FFI that needs no binding layer at all
GDScriptPre-Alpha

Godot's engine IS C++, and since Godot 4 you can add to it without forking it. GDExtension registers a C++ node type the editor lists, inspects and instantiates like a built-in — so this is not a choice between languages but a line drawn through one program. What makes that line cheaper than an embedded scripting language's: both halves share the same object model, so there is no second type system to bridge.

  • Your base class chooses your memory strategyRefCounted is shared_ptr, Object is a raw new with a free() you must call, and Node is freed by its parent; there is no stack allocation and no value semantics
  • Godot has C++'s dangling-pointer problem because underneath it is C++ — but is_instance_valid() is a built-in weak_ptr::expired(), and queue_free() defers destruction to end-of-frame
  • Static typing is opt-in and worth 2-4x: a bare var is std::any, := is auto, and annotating lets the compiler skip Variant dispatch entirely
  • Packed*Array is a real contiguous buffer with no Variant per element — reaching for one is often the difference between "this needs a C++ node" and "this is fine in GDScript"
  • Signals are the observer pattern as a language construct, inspectable in the editor, saved with the scene, and severed automatically when either end is freed — no dangling callbacks
  • GDExtension costs a build step, an ABI tied to the engine version, no hot reload, and a segfault that takes the editor with it — so measure before adding one; typing and packed arrays answer most cases
Drag cards to reorder · your order is saved locally