PONYΞ»M2Modula-2
CodeCompared
for Swift programmers

You already know Swift.Now explore other languages.

Side-by-side, interactive cheatsheets for Swift programmers
comparing Swift 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

Every guarantee traded for expressiveness. No compiler, no types, no optionals, no exhaustiveness β€” and in return, a language where classes are never closed, blocks are everywhere, and methods can be written while the program runs. Swift protects you from yourself; Ruby hands you the keys.

  • Duck typing replaces protocols: anything with the right method works, no conformance declared β€” and a misspelled method is a NoMethodError when that line runs
  • nil is an object with methods (nil.to_a is []), &. is ?., and only nil and false are falsy β€” so 0 and "" are true
  • Blocks are the whole language: File.open(path) { } is Swift's defer expressed as an ordinary method, and Enumerable is the richest collection library on this site
  • Open classes: reopen String or redefine Integer#even? globally β€” extension methods with none of the guardrails (Rails is built on it)
  • Mixins replace protocols and protocol extensions: define <=>, include Comparable, inherit every operator free
  • Metaprogramming is idiomatic, not a hack: define_method and method_missing are how attr_accessor itself works
GoPre-Alpha

A deliberately smaller language, and a different bet on concurrency. No optionals, no enums with payloads, no exceptions, no inheritance, no generics until 1.18 β€” every absence is a design decision. What you get back is a language you can hold in your head, a compiler that finishes instantly, and goroutines and channels: cheap threads over shared memory, where blocking is free and no function is async.

  • The zero value replaces the optional: a missing map key is 0, a missing name is "", and the type system cannot tell "absent" from "empty"
  • Errors are values returned alongside the result, so if err != nil is one line in three β€” the failure path is as visible as the happy one, which is exactly the point
  • Interface conformance is implicit: the consumer defines the interface it needs, after the fact, and any type with matching methods satisfies it
  • No enums with payloads and no exhaustiveness β€” a sum type becomes an interface plus a struct per case, and a missing case falls silently into default
  • Slices alias: second := first shares the backing array, so a write through one is visible through the other (Swift's copy-on-write makes a copy a copy)
  • No actors and no Sendable: you take a sync.Mutex by hand, and a forgotten lock is found by the runtime race detector rather than the compiler
JavaScriptAlpha⚑ Works Offline⚑ Offline

Same async, opposite everything else. async/await is nearly keyword for keyword β€” and underneath, one is cooperative multitasking on a real thread pool with compiler-checked actor isolation, and the other is one thread with a job queue. So the page leads with the convergence and then takes it apart.

  • Task {} becomes a floating promise: no parent, no scope, no cancellation β€” AbortController is a convention you thread through by hand
  • Value semantics disappear. A struct copies; an object never does, and const only freezes the binding
  • Optionals become undefined and null, checked by nothing β€” though ?. and ?? do mean what you expect
  • Exhaustive enums with payloads become a kind field and a switch nobody checks
  • ARC becomes a tracing collector: no weak, no cycles to break β€” and no deinit you can rely on either
  • "πŸ‘¨β€πŸ‘©β€πŸ‘§".count is 1 and .length is 8, because one counts grapheme clusters and the other UTF-16 code units
PythonBeta⚑ Works Offline⚑ Offline

Every compile-time guarantee, traded for speed of writing. No compiler, no optionals, no value types, and type hints that look exactly like Swift annotations and are enforced by nothing at run time. What you get back is the fastest path from idea to working code, and a library for everything.

  • Type hints are not checked at run time β€” double("nope") happily returns "nopenope". A separate tool (mypy/pyright) is the compiler you no longer have; turn it on day one
  • No optionals: None is a value nothing makes you check, and AttributeError: 'NoneType' object has no attribute … is the nil-crash, arriving in production
  • There are no value types β€” assignment always aliases, and a mutable default argument is created once and shared across every call
  • Duck typing replaces protocols: if it has the method it works, and typing.Protocol gives a static checker the structural conformance back
  • match destructures classes, dicts and lists (going beyond switch) β€” but has no exhaustiveness, so a missing case falls silently into case _
  • The GIL means threads give you no parallelism: asyncio for waiting, multiprocessing for computing, threads almost never
DartPre-Alpha

The sibling language that rhymes almost everywhere β€” and misleads exactly where it matters. Dart shares Swift's sound null safety, type inference, arrow bodies, named arguments, and Dart 3 pattern matching, but drops value types entirely: there is no struct, so every class is a reference.

  • No value types β€” a class assignment aliases the same object; reach for records (Dart 3) or a hand-written copyWith where a Swift struct would copy
  • Enums with associated values become sealed class hierarchies β€” Dart’s enum is closer to a Swift raw-value enum and can’t carry per-case data
  • Named parameters are optional by default and live in { } β€” the inverse of Swift’s always-required argument labels; mark them required to force them
  • Integer / always yields a double; truncating division is a separate ~/ operator β€” a genuine trap coming from Swift
  • Cascades (..), collection-if/for, mixins (with), and the late/dynamic escape hatches have no Swift equivalent at all
  • Async/await matches, but concurrency is isolate-based with no shared memory β€” a stronger guarantee than a Swift actor, which shares memory and serializes access
KotlinPre-Alpha

The platform swap, and a language that rhymes almost too well. Designed a year apart and borrowing from each other ever since: null safety with ?, data classes, sealed hierarchies, when/switch, extensions, named arguments with defaults, trailing lambdas, structured concurrency. The risk here is complacency β€” so this page is about the places the rhyme misleads.

  • A data class is not a struct: it is a reference, so second = first aliases and copy() is a method you must remember (and it is shallow)
  • Enums with associated values become sealed classes, not enum class β€” the false friend that costs everyone an afternoon
  • No ARC: cycles are not a leak, weak/unowned have no purpose, and there is no deinit and no defer β€” cleanup is use { }
  • throws vanishes from the signature (all exceptions are unchecked), so nothing tells a caller a function can fail
  • Null safety is as strong as Swift's until the Java boundary, where platform types switch the checking off β€” that is where NPEs in "null-safe" Kotlin come from
  • No actors and no Sendable: a Mutex you take by hand does that job, and forgetting it still compiles
RustPre-Alpha

The shortest trip into systems programming there is. These two languages rhyme constantly β€” Optional is Option, enums with associated values are enums with data, protocols are traits, guard let is let … else, switch is match, and both are memory-safe without a garbage collector. What changes is when the safety is established: ARC does it at run time, and ownership does it at compile time.

  • let second = first is the most different line on the page: Swift copies (structs, copy-on-write), Rust moves β€” the original name becomes unusable, and .clone() is the visible, explicit opt-out
  • The borrow checker permits many readers or one writer, never both β€” so iterator invalidation, use-after-free, and data races are all the same compile error
  • Lifetimes are the genuinely new idea: ARC kept a referent alive at run time, and Rust instead proves statically that your reference cannot outlive it
  • Reference counting is opt-in (Rc/Arc), not the default every class pays for β€” most values have one owner and cost nothing
  • throws/try becomes Result<T, E> and the ? operator: a failure is a value in the return type, so it can be stored, mapped, and collected
  • A Mutex owns its data, so "forgot to take the lock" is unwritable β€” and async ships with no runtime at all, which is the biggest surprise coming from Swift
TypeScriptAlpha⚑ Works Offline⚑ Offline

The surface rhymes β€” ?., ??, inference, generics, async/await β€” but the type system underneath is erased, structural, and gradual. A Swift developer's instincts about casts, force-unwraps, and value types are exactly the ones TypeScript quietly betrays: as checks nothing, ! checks nothing, and every object is a reference.

  • Structural typing: conformance is automatic for anything with the right shape β€” implements is optional documentation, and a bare object literal satisfies any interface it matches
  • Total erasure: as and ! are compile-time claims with no runtime check β€” where Swift's as? and force-unwrap actually test and trap, TypeScript just proceeds
  • Optionals become unions: String? is string | undefined, unwrapping is control-flow narrowing β€” and there are TWO absence values, undefined and null
  • No value types: no structs, no copy-on-write arrays β€” assignment aliases, spread copies shallowly, and === compares references with no Equatable to synthesize equality
  • Enums with associated values become discriminated unions, with exhaustiveness recruited via a never-typed default instead of granted by switch
  • Type-level power Swift lacks: literal types, keyof, mapped types (Partial, Pick), and template literal types compute types from types
  • One thread, no data races: the entire actor/Sendable apparatus dissolves β€” Promise.all plays async let, and parallelism means message-passing workers
C#Pre-Alpha

The closest match in shape that Swift has. The only mainstream target that keeps the struct/class split β€” real value types and reference types β€” plus properties, reified generics, extension methods, pattern matching, and the async/await syntax C# invented and Swift later adopted. Which is exactly why the places it deceives you are worth knowing.

  • string? is not String?: nullable reference types are a compiler analysis that produces warnings and is erased at run time β€” a NullReferenceException is still reachable
  • var means inferred, not mutable β€” it is both of Swift's keywords and carries neither meaning; there is no general-purpose let
  • Structs are value types with no copy-on-write, and indexing a collection of them hands you a copy β€” mutate it and nothing happens
  • Records give you value equality and with expressions (ada with { Age = 37 }) β€” the one thing here that is nicer than Swift
  • No enums with associated values: a C# enum is a named integer (and (Status)99 is legal), so sum types become sealed record hierarchies
  • Pattern matching has overtaken Swift's: property patterns, relational patterns, and list patterns all exist here and do not exist there
JavaPre-Alpha

Everything is a reference again. There is no struct, so every assignment aliases and defensive copying comes back; T? becomes null plus a library class you can forget to use; and generics are erased, so List<String> is a List at run time. The consolations are real: throws meets checked exceptions, and sealed interfaces plus record patterns are your enums with associated values.

  • Value semantics are gone β€” a record is the nearest thing, and it works only because it is immutable; returning a collection hands out the collection itself
  • final is a much weaker let: it stops reassignment, never mutation, so a final List still accepts add
  • Optionals become null, which every reference type admits; Optional<T> is a class for return types, and it does not stop null existing
  • Protocols become interfaces with default methods β€” but conformance is declared at the type, so retroactive conformance is impossible and there are no extensions at all
  • Enums with associated values become sealed interfaces plus records, and a switch over them is exhaustive with record patterns and when guards
  • throws and checked exceptions are the same idea from two eras β€” the one place the languages genuinely agree
  • ARC becomes a tracing collector: no weak, no unowned, no retain cycles β€” and no deinit, so cleanup is try-with-resources
  • Actors and structured concurrency become synchronized, executors and virtual threads, with data races the compiler will not catch
ScalaPre-Alpha

The other language that blends OOP and functional programming under a serious type system. The rhyme is real β€” Option, sealed hierarchies, traits, match, immutability by default β€” so this page is about where Scala goes further: implicits, higher-kinded types, and one for-comprehension that chains every fallible thing you have.

  • Implicits have no Swift counterpart: one mechanism gives you context parameters, extension methods, and type classes the compiler assembles per call site
  • Higher-kinded types (F[_]) abstract over List/Option/Future themselves β€” Swift cannot express the signature at all
  • for … yield is sugar for flatMap, so the same three lines work over Option, Either, Try, List and Future
  • Pattern matching is extensible: any object with an unapply is a pattern, so you can match on a parsed integer instead of parsing before the switch
  • Traits hold state and stack by linearization β€” a protocol extension that can also be a composable decorator
  • The costs: exhaustiveness is a warning (turn on -Xfatal-warnings), a case class is a reference type, and Future is eager with no structured concurrency
RocPre-Alpha

The same bets, pushed one step further β€” and the step is memory-shaped. Reference counting rather than a collector, value semantics with copy-on-write, enums with associated values, exhaustive matching, failure as a value: Roc makes all of them, and then removes the retain cycle, the optional and the declaration a union needs.

  • No retain cycle, so no weak and no unowned. Nothing can be made to point back at a value that already exists, which is exactly the condition under which reference counting alone is sufficient β€” no [weak self], and no leak to chase in Instruments.
  • Copy-on-write on everything, written by nobody. Swift gives it to Array and makes a library author implement isKnownUniquelyReferenced by hand; Roc applies it to every value, because immutability makes the optimization always safe.
  • There is no Optional, because there is nothing to wrap. Absence is a tag that names itself, so a "nothing" can distinguish Missing from NotYetLoaded β€” and there is no ! to force with.
  • Enums without the declaration, and unions that stay open. A tag is a value the moment you write it, and [Go, Stop, ..] means "these two, plus whatever turns up" with the named cases still exhaustively checked β€” which a closed enum cannot express.
  • One channel for failure. Try(ok, err) is Result promoted to the only way, so there is no throws-versus-Result boundary to convert across, and try becomes ?.
  • No struct-versus-class decision, no mutating, no inout, and no indirect on a recursive enum β€” everything is a value and the compiler works out the boxing.
  • Be honest about the trade: no async/await, no actors, no protocol extensions, no grapheme-aware strings, no Codable, no Apple ecosystem, and no 1.0 against Swift's 2014.
Drag cards to reorder Β· your order is saved locally