x = 10 # mutable by default
name = "Alice"
GRAVITY = 9.8 # uppercase = constant convention
x = 20 # reassignment fine
puts x
puts name
puts GRAVITY var x = 10 // mutable
let name = "Alice" // immutable constant
let gravity = 9.8 // constants use let, not uppercase
x = 20 // OK β var is mutable
// name = "Bob" // compile error β let is immutable
print(x)
print(name)
print(gravity) Swift's let creates a truly immutable binding β the compiler enforces it, unlike Ruby's uppercase convention. Use let by default and reach for var only when mutation is needed. The compiler will warn you if a var is never mutated.
name = "World"
count = 42
puts "Hello, #{name}!"
puts "Count is #{count * 2}"
puts "Sum: #{(1..10).sum}" let name = "World"
let count = 42
print("Hello, \(name)!")
print("Count is \(count * 2)")
print("Sum: \((1...10).reduce(0, +))") Ruby uses #{ } for interpolation; Swift uses \( ). Both evaluate arbitrary expressions inside. Swift 5.7+ allows multiline expressions inside interpolations.
# Single-line comment
x = 42 # inline comment
=begin
Multi-line comment block
(rarely used in practice)
=end
puts x // Single-line comment
let x = 42 // inline comment
/* Multi-line comment β
these can be /* nested */
which Ruby's cannot */
print(x) Swift uses C-style // and /* */ comments. Swift's block comments support nesting (/* /* inner */ outer */), which is useful for commenting out regions that already contain comments β impossible in Ruby's =begin/=end.
puts "With newline" # adds \n
print "No newline"; print "\n"
p [1, 2, 3] # inspect: [1, 2, 3]
puts [1, 2, 3] # one element per line print("With newline")
print("No newline", terminator: ""); print("")
print([1, 2, 3]) // [1, 2, 3]
dump([1, 2, 3]) // detailed debug output Swift's print() adds a newline by default. Use the terminator: parameter to suppress it. Swift has no direct equivalent to Ruby's p, but dump() gives detailed recursive output for debugging.
number = 42 # Integer
price = 9.99 # Float
greeting = "Hi" # String
active = true # TrueClass
puts number.class
puts price.class
puts greeting.class let number = 42 // inferred: Int
let price = 9.99 // inferred: Double
let greeting = "Hi" // inferred: String
let active = true // inferred: Bool
// Explicit annotations:
let count: Int = 0
let ratio: Float = 0.5
print(type(of: number), type(of: price)) Swift infers types at compile time and they are fixed β a variable cannot change type after declaration. Ruby's type system is dynamic: any variable can hold any type. Swift's type(of:) is analogous to Ruby's .class.
puts true && false # false
puts true || false # true
puts !true # false
# Only false and nil are falsy in Ruby:
puts "0 is truthy" if 0
puts "empty str truthy" if ""
puts "empty arr truthy" if [] print(true && false) // false
print(true || false) // true
print(!true) // false
// Swift requires strict Bool β no truthiness coercion:
if 1 == 1 { print("must be explicit Bool") }
// if 0 { } // compile error: integer is not a Bool
// if "" { } // compile error: string is not a Bool In Ruby, only false and nil are falsy β 0, "", and [] are all truthy. Swift requires a strict Bool in every condition. This prevents a whole class of subtle bugs where non-nil values are used as conditionals unintentionally.
# Every Ruby variable can be nil
name = nil
name = "Alice"
puts name.nil? # false
puts name&.upcase # safe navigation: ALICE
missing = nil
puts missing&.upcase.inspect # nil β no crash var name: String? = nil // optional String
name = "Alice"
print(name == nil) // false
print(name?.uppercased()) // Optional("ALICE")
let missing: String? = nil
print(missing?.uppercased() as Any) // nil β no crash
// var required: String = nil // compile error! Swift's type system distinguishes String (never nil) from String? (may be nil). Ruby's safe navigation &. maps directly to Swift's optional chaining ?.. The key improvement: in Swift, a non-optional type cannot be nil β the compiler guarantees it.
input = "42"
number = Integer(input) rescue nil
if number
puts "Got: #{number * 2}"
else
puts "Not a number"
end let input = "42"
if let number = Int(input) { // unwrap and bind
print("Got: \(number * 2)") // number is plain Int here
} else {
print("Not a number")
}
// Swift 5.7+ shorthand (same name):
if let number = Int("99") { print(number) } if let simultaneously checks for nil and unwraps the value into a non-optional binding. Inside the block, number is a plain Int, not Int?. Swift 5.7 added the shorthand if let x { } when shadowing the same name.
def process(input)
number = Integer(input) rescue nil
return "invalid" unless number
# number is guaranteed non-nil from here
number * 2
end
puts process("21")
puts process("abc") func process(_ input: String) -> String {
guard let number = Int(input) else {
return "invalid"
}
// number is a plain Int in the enclosing scope
return "\(number * 2)"
}
print(process("21"))
print(process("abc")) guard let is Swift's return unless. Unlike if let, the unwrapped binding is available in the enclosing scope after the guard, not just inside a block. The else branch must exit (return, throw, or break) β the compiler enforces this.
name = nil
display = name || "Anonymous"
puts display
flag = false
result = flag || "default" # replaces false too!
puts result let name: String? = nil
let display = name ?? "Anonymous"
print(display)
// ?? only replaces nil, not false:
let flag: Bool? = false
let result = flag ?? true // result is false!
print(result) Swift's ?? only replaces nil β it does not treat false, 0, or "" as absent. Ruby's || replaces both nil and false. This distinction matters when working with optional booleans.
greeting = "hello world"
puts greeting.upcase
puts greeting.length
puts greeting.include?("world")
puts greeting.split(" ").inspect
puts greeting.gsub("world", "Swift") import Foundation
let greeting = "hello world"
print(greeting.uppercased())
print(greeting.count)
print(greeting.contains("world"))
print(greeting.components(separatedBy: " "))
print(greeting.replacingOccurrences(of: "world", with: "Swift")) Swift string methods are more verbose: uppercased() not upcase, count not length. Methods from Foundation (like components(separatedBy:) and replacingOccurrences) require import Foundation, which is included automatically on Apple platforms.
text = <<~HEREDOC
Line one
Line two
Indentation stripped to match marker
HEREDOC
puts text let text = """
Line one
Line two
Indentation stripped to match closing delimiter
"""
print(text) Both languages strip leading indentation from multiline strings. Ruby uses squiggly heredoc (<<~HEREDOC); Swift uses triple-quoted strings ("""). Swift requires the closing """ on its own line; the amount of indentation it has sets the strip level.
numbers = [1, 2, 3, 4, 5]
numbers.push(6) # or numbers << 6
numbers.pop
puts numbers.first
puts numbers.last
puts numbers.length
puts numbers.include?(3)
puts numbers.inspect var numbers = [1, 2, 3, 4, 5]
numbers.append(6)
numbers.removeLast()
print(numbers.first!) // first returns Int? β unwrap safely
print(numbers.last!)
print(numbers.count)
print(numbers.contains(3))
print(numbers) Swift arrays are typed β [Int], [String], etc. β and cannot mix types without [Any]. first and last return optionals (Int?) since the array might be empty. The ! force-unwrap here is safe only because we know the array is non-empty.
person = {name: "Alice", age: 30}
puts person[:name]
puts person[:age]
person[:email] = "alice@example.com"
puts person.keys.inspect var person = ["name": "Alice", "age": "30"]
print(person["name"] ?? "unknown") // subscript returns String?
print(person["age"] ?? "unknown")
person["email"] = "alice@example.com"
print(Array(person.keys).sorted()) Dictionary subscript in Swift always returns an optional (Value?) since the key might not exist. In Ruby, a missing key silently returns nil. Swift makes the "might be absent" case explicit β you must handle it with ??, if let, or guard let.
numbers = [1, 2, 3, 4, 5]
doubled = numbers.map { |x| x * 2 }
evens = numbers.select { |x| x.even? } # filter
total = numbers.reduce(0) { |sum, x| sum + x }
puts doubled.inspect
puts evens.inspect
puts total let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
let evens = numbers.filter { $0 % 2 == 0 }
let total = numbers.reduce(0) { $0 + $1 }
// or: numbers.reduce(0, +)
print(doubled)
print(evens)
print(total) Swift's map, filter, and reduce mirror Ruby's almost exactly. Swift uses $0, $1 as shorthand argument names (like Ruby's |x|). Note: Ruby calls it select; Swift calls it filter. Swift can use operators like + directly as closures.
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "C or below"
end
puts "pass" if score >= 60 # postfix if let score = 85
if score >= 90 {
print("A")
} else if score >= 80 { // "else if", not "elsif"
print("B")
} else {
print("C or below")
}
print(score >= 60 ? "pass" : "fail") // ternary instead Swift uses else if (two words) where Ruby uses elsif. Braces { } are always required β no then or end. Swift has no postfix if; use the ternary operator for one-liners.
day = "Monday"
case day
when "Saturday", "Sunday"
puts "Weekend"
when /^Mon|Tue|Wed|Thu|Fri/
puts "Weekday"
else
puts "Unknown"
end let day = "Monday"
switch day {
case "Saturday", "Sunday":
print("Weekend")
case "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday":
print("Weekday")
default:
print("Unknown")
} // must be exhaustive β no silent fall-through Swift switch must be exhaustive and does not fall through by default (unlike C). Multiple values per case use commas. Swift's switch is far more powerful than Ruby's β it supports tuples, ranges, value bindings, and where clauses.
(1..5).each { |i| print "#{i} " }; puts
[10,20,30].each_with_index do |val, idx|
print "#{idx}:#{val} "
end; puts
3.times { |i| print "#{i} " } for i in 1...5 { print("\(i) ") }; print("")
for (idx, val) in [10,20,30].enumerated() {
print("\(idx):\(val) ", terminator: "")
}; print("")
for i in 0..<3 { print("\(i) ", terminator: "") } β Range operators are reversed compared to Ruby: Swift's 1...5 is inclusive (like Ruby's 1..5), and 1..<5 is exclusive (like Ruby's 1...5). Swift's enumerated() replaces Ruby's each_with_index.
def greet(name)
"Hello, #{name}!" # implicit return
end
def add(a, b) = a + b # one-liner (Ruby 4.0)
puts greet("Alice")
puts add(3, 4) func greet(name: String) -> String {
"Hello, \(name)!" // single expr: implicit return
}
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(greet(name: "Alice"))
print(add(3, 4)) Swift functions require type annotations and an explicit -> ReturnType. Single-expression bodies return implicitly (Swift 5.1+). The _ before a parameter drops its external label, allowing add(3, 4) instead of the default add(a: 3, b: 4).
def connect(host:, port: 80, ssl: false)
"#{ssl ? 'https' : 'http'}://#{host}:#{port}"
end
puts connect(host: "example.com")
puts connect(host: "api.io", port: 443, ssl: true) func connect(host: String, port: Int = 80,
ssl: Bool = false) -> String {
"\(ssl ? "https" : "http")://\(host):\(port)"
}
print(connect(host: "example.com"))
print(connect(host: "api.io", port: 443, ssl: true)) Swift requires argument labels at every call site by default β callers must write connect(host: ...). This is the opposite of Ruby, where positional arguments are the default. Both languages support default parameter values. Swift's labels make call sites read like natural English.
double = ->(x) { x * 2 }
puts double.call(5)
words = ["hello", "world"]
puts words.map(&:upcase).inspect
puts words.map { |w| w.upcase }.inspect let double = { (x: Int) -> Int in x * 2 }
print(double(5))
let words = ["hello", "world"]
print(words.map { $0.uppercased() }) // $0 = first arg
print(words.map { w in w.uppercased() }) // named arg Swift closures use { args in body } syntax. $0, $1β¦ are shorthand argument names equivalent to Ruby's |x|, |y|. Swift can pass operators directly where a compatible closure is expected: .sorted(by: <) or .reduce(0, +).
def repeat_work(times)
times.times { yield }
end
repeat_work(3) { print "hello " }
puts func repeatWork(_ times: Int, work: () -> Void) {
for _ in 1...times { work() }
}
repeatWork(3) { print("hello ", terminator: "") }
print("") Swift's trailing closure syntax (closure as the last argument, placed outside the parentheses) is directly analogous to Ruby's block syntax. If a function has only a closure argument, the parentheses can be dropped entirely: repeatWork(3) { ... } β just like Ruby blocks.
class Animal
attr_reader :name, :sound
def initialize(name, sound)
@name = name
@sound = sound
end
def speak = "#{@name} says #{@sound}!"
end
dog = Animal.new("Rex", "Woof")
puts dog.speak
puts dog.name class Animal {
let name: String
let sound: String
init(name: String, sound: String) {
self.name = name
self.sound = sound
}
func speak() -> String { "\(name) says \(sound)!" }
}
let dog = Animal(name: "Rex", sound: "Woof")
print(dog.speak())
print(dog.name) Swift stores properties as typed members β no @instance_var convention. init replaces initialize. Properties declared with let are read-only (like attr_reader); var is read-write (like attr_accessor). No end keyword β just closing braces.
class Animal
attr_reader :name, :sound
def initialize(name, sound)
@name = name
@sound = sound
end
def speak = "#{@name} says #{@sound}!"
end
class Dog < Animal
def initialize(name)
super(name, "Woof")
end
def fetch(item) = "#{@name} fetches #{item}!"
end
dog = Dog.new("Buddy")
puts dog.speak # inherited
puts dog.fetch("ball") class Animal {
let name: String
let sound: String
init(name: String, sound: String) {
self.name = name
self.sound = sound
}
func speak() -> String { "\(name) says \(sound)!" }
}
class Dog: Animal { // colon, not <
init(name: String) {
super.init(name: name, sound: "Woof")
}
func fetch(_ item: String) -> String {
"\(name) fetches \(item)!"
}
}
let dog = Dog(name: "Buddy")
print(dog.speak()) // inherited
print(dog.fetch("ball")) Swift uses class Dog: Animal (colon) where Ruby uses class Dog < Animal (angle bracket). super.init() must be called before accessing self. Overriding a method requires the override keyword β the compiler rejects accidental overrides without it.
# Ruby Struct is reference-based
Point = Struct.new(:x, :y)
a = Point.new(1, 2)
b = a # same object!
b.x = 99
puts a.x # 99 β shared reference struct Point {
var x: Int
var y: Int
}
var a = Point(x: 1, y: 2)
var b = a // COPY β value type
b.x = 99
print(a.x) // 1 β independent copy
print(b.x) // 99 Swift structs are value types β assignment copies the data. Classes are reference types β assignment shares the reference. Most Swift standard library types (Array, Dictionary, String) are structs. Prefer structs for plain data; use classes when shared identity or inheritance is needed.
module Describable
def describe
"I am a #{self.class} named #{name}"
end
end
class Person
include Describable
attr_reader :name
def initialize(name) = @name = name
end
puts Person.new("Alice").describe protocol Describable {
var name: String { get } // required property
}
extension Describable {
func describe() -> String { // default implementation
"I am a \(type(of: self)) named \(name)"
}
}
struct Person: Describable {
let name: String
}
print(Person(name: "Alice").describe()) Swift protocols declare a contract (required properties and methods); protocol extensions provide default implementations β analogous to Ruby modules with methods. Protocols work with both classes and structs, while Ruby mixins work only with classes. A type can conform to multiple protocols.
module Direction
NORTH = :north
SOUTH = :south
EAST = :east
WEST = :west
end
direction = Direction::NORTH
puts direction
puts direction == :north enum Direction { case north, south, east, west }
let direction = Direction.north
print(direction)
print(direction == .north)
switch direction {
case .north: print("Going north")
default: print("Other direction")
} Swift enums are first-class types, not symbol conventions. The compiler enforces exhaustive switching over enum cases β if you add a new case, every switch that doesn't have a default breaks at compile time, forcing you to handle the new case everywhere.
# Ruby: simulate with tagged union
Result = Struct.new(:type, :value)
success = Result.new(:ok, 42)
failure = Result.new(:error, "not found")
[success, failure].each do |result|
case result.type
when :ok then puts "Got #{result.value}"
when :error then puts "Error: #{result.value}"
end
end enum Outcome {
case success(Int)
case failure(String)
}
let results: [Outcome] = [.success(42), .failure("not found")]
for result in results {
switch result {
case .success(let value): print("Got \(value)")
case .failure(let message): print("Error: \(message)")
}
} Swift enum cases can carry associated data of any type. This is one of Swift's most powerful features β it elegantly replaces many class hierarchies. Swift's built-in Result<Success, Failure> and Optional<Wrapped> types are implemented exactly this way.
class ParseError < StandardError; end
def parse_age(str)
age = Integer(str)
raise ParseError, "Must be positive" if age < 0
age
rescue ArgumentError
raise ParseError, "Not a number: #{str}"
end
begin
puts parse_age("25")
puts parse_age("abc")
rescue ParseError => error
puts "Error: #{error.message}"
end enum ParseError: Error {
case notANumber(String), mustBePositive
}
func parseAge(_ str: String) throws -> Int {
guard let age = Int(str) else {
throw ParseError.notANumber(str)
}
guard age >= 0 else { throw ParseError.mustBePositive }
return age
}
do {
print(try parseAge("25"))
print(try parseAge("abc"))
} catch ParseError.notANumber(let str) {
print("Error: Not a number: \(str)")
} catch { print("Error: \(error)") } Swift uses throws/throw/try/do-catch where Ruby uses raise/rescue/begin-end. Throwing functions must be marked throws β callers see at compile time that an error can occur and must handle it explicitly with try.
# Ruby: open classes (monkey patching)
class Integer
def factorial
return 1 if self <= 1
self * (self - 1).factorial
end
end
class String
def palindrome? = self == self.reverse
end
puts 5.factorial
puts "racecar".palindrome? extension Int {
func factorial() -> Int {
self <= 1 ? 1 : self * (self - 1).factorial()
}
}
extension String {
var isPalindrome: Bool { self == String(self.reversed()) }
}
print(5.factorial())
print("racecar".isPalindrome) Swift extension is the structured equivalent of Ruby's open classes. Extensions can add methods, computed properties, and protocol conformances to any type β including built-ins β without modifying the original source. Unlike Ruby, Swift extensions cannot add stored (instance variable) properties.
thread1 = Thread.new { "result 1" }
thread2 = Thread.new { "result 2" }
puts thread1.value
puts thread2.value import Foundation
func fetchData(_ label: String) async -> String {
try? await Task.sleep(nanoseconds: 10_000_000)
return "result \(label)"
}
let results = await withTaskGroup(of: String.self) { group in
group.addTask { await fetchData("1") }
group.addTask { await fetchData("2") }
var collected: [String] = []
for await value in group { collected.append(value) }
return collected.sorted()
}
print(results.joined(separator: "\n")) Swift 6 has structured concurrency built into the language. async/await is similar to modern Ruby using the Async gem. Swift 6's strict concurrency mode makes data races compile-time errors β a major safety improvement over Ruby's GIL-based threading model.
# In Ruby, let is only an RSpec helper β all variables mutate freely
x = 1
x = 2 # fine
x += 1 # fine
puts x let x = 1
// x = 2 // compile error: cannot assign to let constant
// x += 1 // compile error
var y = 1
y = 2 // fine: var is mutable
y += 1
print(y) The most common early stumble: Swift's let creates an immutable constant that the compiler enforces at compile time. In Ruby, let has no special meaning outside RSpec. Declare everything with let by default β the compiler will tell you when you actually need var.
puts (1..5).to_a.inspect # [1,2,3,4,5] inclusive (..)
puts (1...5).to_a.inspect # [1,2,3,4] exclusive (...) print(Array(1...5)) // [1,2,3,4,5] inclusive (...)
print(Array(1..<5)) // [1,2,3,4] exclusive (..<)
// Ruby's ... and Swift's ... mean OPPOSITE things! β High off-by-one risk: Ruby's ... excludes the endpoint; Swift's ... includes it. For exclusive ranges, Swift uses ..< (half-open). They look nearly identical but mean opposite things β double-check every range when porting logic between languages.
class Config
attr_accessor :timeout, :host
# @timeout and @host default to nil β no error
end
cfg = Config.new
puts cfg.timeout.inspect # nil
puts cfg.host.inspect # nil struct Config {
var timeout: Int // must be initialized β no default nil
var host: String? // ? explicitly declares "may be nil"
}
// let cfg = Config() // compile error: missing timeout
let cfg = Config(timeout: 30, host: nil)
print(cfg.timeout) // 30
print(cfg.host as Any) // nil Swift requires every non-optional property to have a value before the object is used. You cannot leave a property uninitialised and silently get nil back. If a property is genuinely optional, declare it as Type?. This eliminates entire categories of "undefined method for nil:NilClass" errors.
numbers = [1, 2, 3]
copy = numbers # same array object!
copy << 4
puts numbers.inspect # [1, 2, 3, 4] β mutated!
puts numbers.equal?(copy) # true β same object var numbers = [1, 2, 3] // Array is a struct β value type
var copy = numbers // COPY created here
copy.append(4)
print(numbers) // [1, 2, 3] β unchanged
print(copy) // [1, 2, 3, 4] Swift's Array, Dictionary, and String are all structs (value types), so assignment creates an independent copy. Ruby's equivalent types are objects (reference types), so assignment shares the reference. Mutating a Swift array after assigning it to another variable does not affect the original.