Side-by-side, interactive cheatsheets for Swift programmers
comparing Swift to other languages. Every example runs live in your browser β no setup, no installation.
Choose your own path by reordering languages
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.
NoMethodError when that line runsnil is an object with methods (nil.to_a is []), &. is ?., and only nil and false are falsy β so 0 and "" are trueFile.open(path) { } is Swift's defer expressed as an ordinary method, and Enumerable is the richest collection library on this siteString or redefine Integer#even? globally β extension methods with none of the guardrails (Rails is built on it)<=>, include Comparable, inherit every operator freedefine_method and method_missing are how attr_accessor itself worksA 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.
0, a missing name is "", and the type system cannot tell "absent" from "empty"if err != nil is one line in three β the failure path is as visible as the happy one, which is exactly the pointdefaultsecond := 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)Sendable: you take a sync.Mutex by hand, and a forgotten lock is found by the runtime race detector rather than the compilerSame 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 handstruct copies; an object never does, and const only freezes the bindingundefined and null, checked by nothing β though ?. and ?? do mean what you expectkind field and a switch nobody checksweak, 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 unitsEvery 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.
double("nope") happily returns "nopenope". A separate tool (mypy/pyright) is the compiler you no longer have; turn it on day oneNone is a value nothing makes you check, and AttributeError: 'NoneType' object has no attribute β¦ is the nil-crash, arriving in productiontyping.Protocol gives a static checker the structural conformance backmatch destructures classes, dicts and lists (going beyond switch) β but has no exhaustiveness, so a missing case falls silently into case _asyncio for waiting, multiprocessing for computing, threads almost neverThe 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.
copyWith where a Swift struct would copysealed class hierarchies β Dartβs enum is closer to a Swift raw-value enum and canβt carry per-case data{ } β the inverse of Swiftβs always-required argument labels; mark them required to force them/ always yields a double; truncating division is a separate ~/ operator β a genuine trap coming from Swift..), collection-if/for, mixins (with), and the late/dynamic escape hatches have no Swift equivalent at allThe 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.
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)enum class β the false friend that costs everyone an afternoonweak/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 failSendable: a Mutex you take by hand does that job, and forgetting it still compilesThe 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-outRc/Arc), not the default every class pays for β most values have one owner and cost nothingthrows/try becomes Result<T, E> and the ? operator: a failure is a value in the return type, so it can be stored, mapped, and collectedMutex 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 SwiftThe 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.
implements is optional documentation, and a bare object literal satisfies any interface it matchesas and ! are compile-time claims with no runtime check β where Swift's as? and force-unwrap actually test and trap, TypeScript just proceedsString? is string | undefined, unwrapping is control-flow narrowing β and there are TWO absence values, undefined and null=== compares references with no Equatable to synthesize equalitynever-typed default instead of granted by switchkeyof, mapped types (Partial, Pick), and template literal types compute types from typesSendable apparatus dissolves β Promise.all plays async let, and parallelism means message-passing workersThe 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 reachablevar means inferred, not mutable β it is both of Swift's keywords and carries neither meaning; there is no general-purpose letwith expressions (ada with { Age = 37 }) β the one thing here that is nicer than Swiftenum is a named integer (and (Status)99 is legal), so sum types become sealed record hierarchiesEverything 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.
record is the nearest thing, and it works only because it is immutable; returning a collection hands out the collection itselffinal is a much weaker let: it stops reassignment, never mutation, so a final List still accepts addnull, which every reference type admits; Optional<T> is a class for return types, and it does not stop null existingdefault methods β but conformance is declared at the type, so retroactive conformance is impossible and there are no extensions at allswitch over them is exhaustive with record patterns and when guardsthrows and checked exceptions are the same idea from two eras β the one place the languages genuinely agreeweak, no unowned, no retain cycles β and no deinit, so cleanup is try-with-resourcessynchronized, executors and virtual threads, with data races the compiler will not catchThe 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.
F[_]) abstract over List/Option/Future themselves β Swift cannot express the signature at allfor β¦ yield is sugar for flatMap, so the same three lines work over Option, Either, Try, List and Futureunapply is a pattern, so you can match on a parsed integer instead of parsing before the switch-Xfatal-warnings), a case class is a reference type, and Future is eager with no structured concurrencyThe 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.
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.Array and makes a library author implement isKnownUniquelyReferenced by hand; Roc applies it to every value, because immutability makes the optimization always safe.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.[Go, Stop, ..] means "these two, plus whatever turns up" with the named cases still exhaustively checked β which a closed enum cannot express.Try(ok, err) is Result promoted to the only way, so there is no throws-versus-Result boundary to convert across, and try becomes ?.mutating, no inout, and no indirect on a recursive enum β everything is a value and the compiler works out the boxing.Codable, no Apple ecosystem, and no 1.0 against Swift's 2014.