Side-by-side, interactive cheatsheets for C programmers
comparing C to other languages. Every example runs live in your browser — no setup, no installation.
Choose your own path by reordering languages
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.
.upcase, .split, .gsub built in; no null terminator to trackC 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.
(result, error) pairs; no global state, no setjmp/longjmpEverything 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.
&, |, << silently truncate to 32 bitsvoid * context, because a function carries the variables it capturedArrayBuffer and the typed arrays are the C-shaped corner: real bytes, real widths, real DataView endianness controlawait 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 trustEverything 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.
2 ** 70 is exact, and every arithmetic operation is a method call on a heap objectis compares identity and == compares value — C's pointer comparison versus memcmp, with both spellable and one character apartEverything 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.
String can never be null, String? might be, and the compiler will not let you confuse them& — everything non-primitive is a reference, and the collector decides when it goes awaydata class generates the equality, hashing and printing you would write by hand for a struct, in one linecinterop, which reads a header and generates the declarationsC'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.
Result and Option replace errno, NULL returns, and setjmp — every error path is explicit and the compiler enforces handling itmatch — no fallthrough, no missed cases, destructuring of any typeextern "C" and #[repr(C)] let you replace individual C files in an existing project one at a timeThe 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.
&+ is there when you actually want wrappingfree to writestruct is a value type with copy-on-write: it copies like a C struct but a large one does not copy until writtenUnsafePointer, UnsafeMutableRawPointer and withUnsafeBytes are still there for the byte-level work, quarantined behind their namesWhat 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.
malloc for an arena or stack allocator without touching library code!T) — every error path is visible in the return type; no unchecked errno, no silent failures, exhaustive handling enforced by the compiler@cImport — import any C header directly; use existing C libraries without writing manual bindingsYour 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(pointer, count) parameter pair collapses to one argumentnew with no matching free — and using for the handles the collector does not manage, which is the goto cleanup ladder made exception-safeSpan<T> is a pointer and a length with the length enforced; stackalloc is alloca with a type attachedSQUARE(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 callThe 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.
&, no casting to an integer — which is exactly what lets the collector move objects and rewrite the referencesint 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 JNIchar is a 16-bit UTF-16 code unit, not a byte: byte[] is what corresponds to char*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.
\$value echoes &value, and $$reference echoes *pointer=~, s///) — C has no regex support at all without <regex.h> or a third-party librarypush/pop — no manual realloc, no separately-tracked capacity, no array-to-pointer decay losing the lengthThe 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.
lua_State, the virtual stack, and lua_CFunction let your C program run Lua and let Lua call straight back into your C functionsmalloc/free — no ownership discipline to maintain, no dangling pointers, no leaks to huntrequire loads a real module at runtime instead of pasting text into your translation unitnil and false are falsy, so 0 is true — the most dangerous C habit to unlearnstdarg.h, and no out-parametersC 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.
class is a struct with methods and access controlunique_ptr, shared_ptr) — deterministic resource cleanup replacing manual free() and fclose()vector, map, sort, transform, ranges — type-safe, zero-overhead containers and algorithms replacing your hand-rolled C equivalentsstd::format, structured bindings, std::optional, std::expected — ergonomics that close the gap with higher-level languagesThe 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.
a(i,j) and a[j][i] name the same element, and getting that backwards is the classic interop bugc = a + b, sum(a), matmul(a, b) — with no loop to write and no aliasing to worry about& is gone and intent(in) is how you say constallocatable arrays are deallocated at end of scope; there is no free to forget and no dangling handle to keepiso_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 underneathC'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 assignmentcase branch does not fall through, and there is no break to forgetset of put in the type system what C leaves to assert and bit-twiddling with #define flagsstrcpy, no terminator, no buffer to sizeA 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.
char *pack and unpack are the struct-over-the-wire tools, and ord/chr/str_repeat cover the byte fiddlingstrict_types to stop the coercionsWhat 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.
#include, no header guards, no order-of-declaration rules; each file is a self-contained modulemalloc/free; @nogc lets you opt out completely for hot pathsstrlen(s) and s.strlen() are identical; method chaining without C++ class machinerystd.algorithm — lazy, composable iteration (filter, map, sort) replacing hand-written C loopsunittest blocks and contract programming (in/out/invariant) — testing and precondition checking without an external frameworkextern(C); the C standard library is available as core.stdc.*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-callConstraint_Error instead of corrupting memorysubtype Grade is Integer range 0..100; makes an invalid value a runtime error everywhere, not just where a manual if check was rememberedint-to-double coercion — every conversion is explicit and visible at the call site.ads) replace header files — no preprocessor, no #ifndef guards, and the compiler checks the body actually matchesexception/raise/when replace return-code and errno-style error handling — an error can never be silently ignoredThe 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.
switch only matches literal integers/charactersbadarith 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 allWhat 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.
[]T) carry their length, so arrays never decay and len is always right — no more passing a count alongside a pointerdefer replaces the goto cleanup ladder: each release is written beside its allocation and runs at scope exit by any pathcontext carries the allocator, so installing an arena redirects make, append, maps, and the whole standard library with one assignment= --- to opt out-1 > 1u cannot quietly be true; signed overflow is defined to wrap rather than undefinedThe 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.
[rbp - 8] — the prologue push rbp / mov rbp, rsp / sub rsp, N is a C stack frame written outrdi, rsi, rdx, rcx, r8, r9, return in rax — and nothing enforces itpointer + 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 typestrlen 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 allrsp simply moves back and the bytes sit there until the next call overwrites them