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

What C programmers reach for when they want to stop managing memory. Ruby's runtime handles everything C makes you do by hand — strings as objects, arrays that resize themselves, a garbage-collected heap — freeing you to describe the problem instead of the machine that solves it.

  • Garbage collection — no malloc, no free, no use-after-free; memory is managed for you
  • Strings as first-class objects — .upcase, .split, .gsub built in; no null terminator to track
  • Dynamic typing — no type declarations; variables hold any object at any time
  • Blocks and closures — pass behavior the way C passes function pointers, but with lexical scope captured automatically
  • Open classes — add methods to any class, including built-ins like Integer and String, at runtime
GoPre-Alpha

C reimagined for the networked world. Go keeps C's simplicity and compile speed while adding goroutines, garbage collection, and a standard library built for servers and CLIs.

  • Goroutines — thousands of concurrent tasks at ~2 KB each; C threads cost megabytes and require pthreads boilerplate
  • Garbage collection — no malloc/free, no use-after-free, no double-free; the GC handles memory without a runtime you have to ship
  • Interfaces without headers — define the methods, satisfy the interface; no vtable declarations, no forward declarations
  • Error values instead of errno — functions return (result, error) pairs; no global state, no setjmp/longjmp
  • Compiles a full program in seconds, produces a single static binary with no shared-library dependencies
  • Direct C interop via cgo — call any C function or link any C library from Go code
JavaScriptAlpha⚡ Works Offline⚡ Offline

Everything C pins down at compile time, JavaScript decides while running. No declared types, no compile step, no addresses — and one numeric type that is a double until you ask for BigInt.

  • There is exactly one number type and it is an IEEE 754 double, so integers are exact only to 2⁵³ and &, |, << silently truncate to 32 bits
  • An object is a hash table with a literal syntax — the struct, the symbol table, and the dictionary you would hand-roll are the same construct
  • Closures replace the function pointer plus its void * context, because a function carries the variables it captured
  • ArrayBuffer and the typed arrays are the C-shaped corner: real bytes, real widths, real DataView endianness control
  • Nothing blocks — the event loop and await take the place of the read-and-wait call, and there is only one thread to lose
  • == converts before comparing and === does not, which is the first habit to build and the last one to trust
PythonBeta⚡ Works Offline⚡ Offline

Everything is a heap object with a type and a reference count. Almost every difference from C follows from that one fact — and the reader's realistic destination is writing the fast part in C, through an extension module or ctypes.

  • A name is a label on an object, never storage you own: assignment rebinds the label rather than copying into a slot, which is why aliasing a list has no syntax to warn you
  • Integers do not overflow — they are objects that grow — so 2 ** 70 is exact, and every arithmetic operation is a method call on a heap object
  • A list is a growable vector of POINTERS to objects, not a contiguous array of values, which is why it can hold mixed types and why NumPy exists
  • is compares identity and == compares value — C's pointer comparison versus memcmp, with both spellable and one character apart
  • CPython reclaims by reference counting, so an object dies at the statement dropping its last reference — deterministic in a way a tracing collector is not, and the thing a C extension must join in with by hand
KotlinPre-Alpha

Everything about the machine is handled for you, and the type system spends its effort on what can be absent. A managed runtime, no pointers, no manual memory — and null tracked in the types so the dereference that would segfault will not compile.

  • Nullability is part of the type: String can never be null, String? might be, and the compiler will not let you confuse them
  • No pointers and no & — everything non-primitive is a reference, and the collector decides when it goes away
  • data class generates the equality, hashing and printing you would write by hand for a struct, in one line
  • Collections are in the library and immutable by default, so the grow-a-buffer loop and the hand-rolled hash table both disappear
  • Coroutines give you thousands of concurrent operations on a handful of threads, with no stack to size and no callback to thread through
  • The way back to C is JNI on the JVM, or Kotlin/Native's cinterop, which reads a header and generates the declarations
RustPre-Alpha

C's speed and control, with memory safety enforced by the compiler. Rust eliminates use-after-free, buffer overruns, and data races — at compile time, with no garbage collector and no runtime overhead.

  • Ownership and borrowing — the compiler proves memory safety; the bugs C programmers lose weeks to (UAF, dangling pointers, data races) are compile errors
  • No undefined behavior — out-of-bounds, integer overflow, and null dereferences that silently corrupt C programs are caught at compile time or checked in debug builds
  • Zero-cost abstractions — iterators, generics, and traits compile to the same assembly as hand-written C; you pay for what you use
  • Result and Option replace errno, NULL returns, and setjmp — every error path is explicit and the compiler enforces handling it
  • Pattern matching with exhaustive match — no fallthrough, no missed cases, destructuring of any type
  • Drop-in C interop — extern "C" and #[repr(C)] let you replace individual C files in an existing project one at a time
SwiftPre-Alpha

The systems language that can read your headers. Swift imports a C header directly and calls the functions in it — and everywhere else it removes the failure modes: no NULL, no uninitialized read, no silent overflow, no dangling pointer.

  • Optionals replace the null pointer: a value that might be absent has a different type, and the compiler makes you handle it
  • Arithmetic overflow traps instead of being undefined — &+ is there when you actually want wrapping
  • Automatic reference counting frees deterministically at the last release, so there is no collector pause and no free to write
  • struct is a value type with copy-on-write: it copies like a C struct but a large one does not copy until written
  • UnsafePointer, UnsafeMutableRawPointer and withUnsafeBytes are still there for the byte-level work, quarantined behind their names
  • A C header becomes an importable module — no binding generator, no wrapper layer, no separate build step
ZigPre-Alpha

What C would look like if designed today. Zig keeps C's simplicity and direct hardware access while replacing the preprocessor, adding explicit allocators, and making every error path visible in the type system.

  • Comptime replaces the C preprocessor — type-safe, debuggable metaprogramming using ordinary Zig code; no macro pitfalls, no token pasting
  • Explicit allocators — every function that allocates takes an allocator parameter; swap malloc for an arena or stack allocator without touching library code
  • Error unions (!T) — every error path is visible in the return type; no unchecked errno, no silent failures, exhaustive handling enforced by the compiler
  • No undefined behavior in safe modes — integer overflow, out-of-bounds access, and null dereference that silently corrupt C programs are caught at runtime in debug builds
  • @cImport — import any C header directly; use existing C libraries without writing manual bindings
  • C ABI compatible — replace individual C files in an existing project one at a time; Zig and C objects link together without ceremony
C#Pre-Alpha

Your statement syntax on a managed runtime — and the pointers are still there when you need them. Braces, semicolons, for, switch and the operators all survived; malloc/free, the preprocessor and undefined behavior did not.

  • int is exactly 32 bits on every platform, long is 64, and char is 16 — the widths are in the language, not in the ABI
  • Every array carries its own length and checks every subscript, so the (pointer, count) parameter pair collapses to one argument
  • new with no matching free — and using for the handles the collector does not manage, which is the goto cleanup ladder made exception-safe
  • Span<T> is a pointer and a length with the length enforced; stackalloc is alloca with a type attached
  • No function-like macros at all, so SQUARE(counter++) has no way to exist — #if is all that is left of the preprocessor
  • [StructLayout], Marshal and [DllImport] are the door back: declare a C signature and the runtime builds the call
JavaPre-Alpha

The language Java kept the syntax of and replaced underneath. Braces, semicolons, for, the operators and even the comment style survived intact; pointers, the preprocessor, manual memory and undefined behavior did not.

  • A reference is a pointer you can only follow — no arithmetic, no &, no casting to an integer — which is exactly what lets the collector move objects and rewrite the references
  • An array carries its own length, so no function needs a count parameter and no index can run past the end unnoticed
  • int is 32 bits everywhere by specification, so sizeof was removed along with the question — but there are no unsigned types at all, which is the first thing to bite over JNI
  • A Java char is a 16-bit UTF-16 code unit, not a byte: byte[] is what corresponds to char*
  • Undefined behavior is gone as a category — overflow wraps, shift counts are taken modulo the width, evaluation order is left to right, and a misbehaving program misbehaves identically everywhere
PerlPre-Alpha

References were deliberately modeled after your pointers — and the memory management headaches are gone. Perl's \/$$ reference syntax deliberately echoes C's &/*, but reference counting reclaims memory automatically: no malloc/free, no leaks, no use-after-free, no dangling pointers.

  • References were deliberately designed to parallel pointers — \$value echoes &value, and $$reference echoes *pointer
  • No malloc/free anywhere — reference counting reclaims every value automatically the instant nothing refers to it anymore
  • Hashes are a built-in language feature — no hand-rolled hash table, bucket array, or third-party library required the way C needs
  • Regular expressions are language syntax (=~, s///) — C has no regex support at all without <regex.h> or a third-party library
  • Arrays grow and shrink dynamically with push/pop — no manual realloc, no separately-tracked capacity, no array-to-pointer decay losing the length
  • No static types at all — every scalar can hold a number, a string, or a reference at different moments, with no declaration or cast required
LuaPre-Alpha⚡ Works Offline⚡ Offline

The scripting language built to be embedded in C. The entire interpreter is a small ANSI C library you link into your own program, and the two sides meet at one lua_State and a virtual stack.

  • The C API is the whole point — lua_State, the virtual stack, and lua_CFunction let your C program run Lua and let Lua call straight back into your C functions
  • A garbage collector instead of malloc/free — no ownership discipline to maintain, no dangling pointers, no leaks to hunt
  • One data structure does everything: the table replaces arrays, structs, and hash tables at once — and it indexes from 1, not 0
  • No preprocessor and no header files — require loads a real module at runtime instead of pasting text into your translation unit
  • Only nil and false are falsy, so 0 is true — the most dangerous C habit to unlearn
  • Multiple return values, closures, and varargs with no function pointers, no stdarg.h, and no out-parameters
C++Pre-Alpha

C with a full systems-programming tower on top. C++ adds classes, templates, RAII, and the STL while staying zero-overhead and fully compatible with every C library and C ABI you already use.

  • Full binary compatibility — every C header, library, and ABI works unchanged; C++ is a strict superset of C for linking purposes
  • Classes and virtual dispatch — OOP built directly on top of C structs; a class is a struct with methods and access control
  • Templates — generic code resolved entirely at compile time; no boxing, no type erasure, no runtime overhead
  • RAII and smart pointers (unique_ptr, shared_ptr) — deterministic resource cleanup replacing manual free() and fclose()
  • The STL: vector, map, sort, transform, ranges — type-safe, zero-overhead containers and algorithms replacing your hand-rolled C equivalents
  • Modern C++23: std::format, structured bindings, std::optional, std::expected — ergonomics that close the gap with higher-level languages
FortranPre-Alpha

The other survivor from the era of the machine — and it disagrees with C about almost every layout decision. Arrays are column-major, subscripts start at 1, arguments arrive by reference, and the array is a first-class value you can add to another array.

  • Column-major storage and 1-based subscripts: a(i,j) and a[j][i] name the same element, and getting that backwards is the classic interop bug
  • Whole-array arithmetic — c = a + b, sum(a), matmul(a, b) — with no loop to write and no aliasing to worry about
  • Arguments are passed by reference by default, so the & is gone and intent(in) is how you say const
  • allocatable arrays are deallocated at end of scope; there is no free to forget and no dangling handle to keep
  • Modules replace headers: the compiler checks every call against a real interface instead of trusting a pasted declaration
  • iso_c_binding makes the two languages agree exactly — c_int, c_ptr, bind(c) — which is why the numeric core of so much C software is Fortran underneath
PascalPre-Alpha

C's contemporary, designed from the opposite instinct. Same era, same structured-programming goal, and a deliberate answer to nearly every place C chose brevity over checking — begin/end, :=, typed pointers, and ranges the compiler enforces.

  • = compares and := assigns, so if (x = 1) cannot be an accidental assignment
  • A case branch does not fall through, and there is no break to forget
  • Pointers are typed and have no arithmetic — you cannot walk one off the end of anything by mistake
  • Subrange types and set of put in the type system what C leaves to assert and bit-twiddling with #define flags
  • Strings carry their length and are assigned whole; no strcpy, no terminator, no buffer to size
  • Units replace the header/source split: one file, one interface section, and no include guards anywhere
PHPPre-Alpha⚡ Works Offline⚡ Offline

A language written in C, whose standard library still shows it. printf, sprintf, fopen, strlen, memcpy-shaped substr — the names came straight across, and then everything underneath them changed.

  • A PHP string is a counted byte string: length stored, no terminator, binary-safe — the representation you would have built to replace char *
  • One container does the work of arrays, structs and hash tables at once, and it remembers insertion order
  • Reference counting frees most values the moment the last name goes away, so the timing is more predictable than a tracing collector
  • pack and unpack are the struct-over-the-wire tools, and ord/chr/str_repeat cover the byte fiddling
  • Types are optional but real: declare them and the engine checks them at the boundary, with strict_types to stop the coercions
  • When PHP is not enough the answer is still C — an extension is a C file against the Zend API, which is why the two meet so often
DPre-Alpha

What C could have grown into. D keeps C's performance and low-level access while adding a module system, garbage collection, ranges, built-in unit tests, and a metaprogramming system that replaces the preprocessor — without dragging in C++'s complexity.

  • Module system replaces headers — no #include, no header guards, no order-of-declaration rules; each file is a self-contained module
  • Garbage collected by default, manual memory available — everyday code needs no malloc/free; @nogc lets you opt out completely for hot paths
  • UFCS (Universal Function Call Syntax) — strlen(s) and s.strlen() are identical; method chaining without C++ class machinery
  • Ranges and std.algorithm — lazy, composable iteration (filter, map, sort) replacing hand-written C loops
  • Built-in unittest blocks and contract programming (in/out/invariant) — testing and precondition checking without an external framework
  • Direct C interop — link any C library, call any C function with extern(C); the C standard library is available as core.stdc.*
AdaPre-Alpha

Everything C leaves to programmer discipline, Ada enforces at compile time or runtime — no manual free() to forget, no silent buffer overrun, no undefined behavior on overflow.

  • access types replace raw pointers — new allocates, but there is no free() to forget or double-call
  • Every array index is bounds-checked automatically; an out-of-range access raises catchable Constraint_Error instead of corrupting memory
  • subtype Grade is Integer range 0..100; makes an invalid value a runtime error everywhere, not just where a manual if check was remembered
  • Strong typing means no implicit int-to-double coercion — every conversion is explicit and visible at the call site
  • Package specs (.ads) replace header files — no preprocessor, no #ifndef guards, and the compiler checks the body actually matches
  • exception/raise/when replace return-code and errno-style error handling — an error can never be silently ignored
ErlangPre-Alpha

The most extreme contrast on the whole site — and the BEAM that runs Erlang is itself written in C. C is statically typed but a bug can trigger undefined behavior; Erlang is dynamically typed but crashes cleanly and predictably, every time, on every platform. C has no automatic memory management, no pattern matching, and no built-in concurrency; Erlang has a garbage collector per process, pattern matching as a core control-flow tool, and isolated, share-nothing processes as its signature feature.

  • No malloc/free anywhere — each process has its own independent heap, garbage collected automatically and separately from every other process
  • Pattern matching destructures a value's shape directly — genuinely new capability C has no equivalent for, where C's switch only matches literal integers/characters
  • No pointers, no addresses, no dereferencing — every value is manipulated purely by binding, matching, and passing
  • A badarith error from dividing by zero is a well-defined, catchable runtime error every time — the same operation in C is undefined behavior with no guaranteed outcome at all
  • Lightweight, isolated processes communicating only by message passing replace C's shared-memory OS threads and manual mutex/lock discipline
  • Formally GUARANTEED tail-call optimization — C's tail-call elimination is merely an optional compiler courtesy with no standard guarantee at any optimization level
OdinPre-Alpha

What C would look like if it were designed today. Odin began as an attempt to write a preprocessor for C, and nearly every feature answers a specific C complaint — the preprocessor itself, int *a, b, arrays decaying to pointers, uninitialized locals, and untagged unions.

  • No preprocessor and no header files — a package is a directory, declaration order is free, and a function-like macro becomes an ordinary type-checked procedure
  • Slices ([]T) carry their length, so arrays never decay and len is always right — no more passing a count alongside a pointer
  • defer replaces the goto cleanup ladder: each release is written beside its allocation and runs at scope exit by any path
  • An implicit context carries the allocator, so installing an arena redirects make, append, maps, and the whole standard library with one assignment
  • Tagged unions store and check the variant for you, and every variable is zero-initialized unless you write = --- to opt out
  • No implicit conversions and no integer promotion, so -1 > 1u cannot quietly be true; signed overflow is defined to wrap rather than undefined
AssemblyPre-Alpha

The layer directly beneath your C. Every construct you write compiles into these instructions, and reading them answers questions the language cannot: what a struct field costs, why the stack must be 16-byte aligned, what printf is hiding, and what "zero-cost" actually means.

  • Your local variables are registers, or slots at [rbp - 8] — the prologue push rbp / mov rbp, rsp / sub rsp, N is a C stack frame written out
  • A parameter list is a promise about registers: rdi, rsi, rdx, rcx, r8, r9, return in rax — and nothing enforces it
  • pointer + 1 does NOT scale here; C multiplies by sizeof for you, which is the clearest proof that a C pointer is an address plus a type
  • strlen is a loop looking for a zero byte, so its cost is visible — while the write syscall takes an explicit length and never looks for a terminator at all
  • Returning a pointer to a local "works" so often because nothing is erased when a frame is freed — rsp simply moves back and the bytes sit there until the next call overwrites them
Drag cards to reorder · your order is saved locally