Structs in Swift: The Essential Building Blocks of Your Code
When we start programming, we quickly realize the need to organize our data and the actions we can perform on them. How can we represent a “User” with their name and email, a “Product” in an online store with price and stock, or even a simple “Point” on a screen with X and Y coordinates? We need a way to group related information into logical, reusable units. In Swift, one of the most fundamental tools for this task is the struct. Along with class and enum, structs form the backbone of data modeling in the language. They’re far more than just “boxes” to store data—they’re powerful and versatile components. In this post, we’ll dive into the world of structs. We’ll explore what they are, how to create them, what they can contain, and—most importantly—we’ll uncover their defining trait: being a Value Type, and why that’s such a crucial (and often advantageous) concept in Swift development. You might be surprised to find that structs are, in many cases, the preferred choice for building the foundations of your code. What Is a Struct? The Blueprint of Data Think of a struct as a blueprint or mold. It defines a new, custom data type by specifying which properties (data it will hold) and methods (actions it can perform) are associated with that type. Analogy: Imagine a house blueprint (struct House). It defines that a house will have a number of bedrooms (bedrooms: Int), an area (area: Double), and perhaps a color (color: String). It can also define actions like paint(newColor: String) or calculateTax() (methods). Each house built from this blueprint (let myHouse = House(...)) is an instance of the struct. The main goals of structs are to help us write code that is: Organized: Groups related data and behavior together Readable: Models real-world concepts or abstractions clearly Reusable: Defines a type that can be used across your project Creating Your First Struct: Hands On The syntax to define a struct is straightforward, using the struct keyword: struct Movie { let title: String var director: String let releaseYear: Int var userRating: Double? var watched: Bool = false } To create an actual object (an instance) from this "blueprint", you can do the following: var myFavoriteMovie = Movie(title: "Interstellar", director: "Christopher Nolan", releaseYear: 2014, userRating: 9.5) let anotherMovie = Movie(title: "Inception", director: "Christopher Nolan", releaseYear: 2010, userRating: 9.2, watched: true) That Movie(...) is a memberwise initializer. For structs, if you don’t define any custom init, Swift automatically provides one that accepts an argument for each stored property (except those with default values, which are optional). Once you have an instance, you can access its properties using dot notation: print(myFavoriteMovie.title) // Output: Interstellar myFavoriteMovie.watched = true myFavoriteMovie.userRating = 10.0 print("Watched \(myFavoriteMovie.title)? \(myFavoriteMovie.watched)") // Output: true // Trying to modify a 'let' property causes an error: // myFavoriteMovie.releaseYear = 2015 // ERROR! // Trying to modify any property of a 'let' instance also causes an error: // anotherMovie.director = "Unknown" // ERROR! What Can a Struct Contain? Structs can include more than just stored properties. 1. Properties Stored Properties: Directly store a value in the instance, like title, director, etc. Computed Properties: Do not store a value but compute it based on other properties. They are always declared with var, and you can define a get (and optionally set) block. struct Circle { var radius: Double var diameter: Double { return radius * 2 } var area: Double { get { return Double.pi * radius * radius } set(newArea) { guard newArea >= 0 else { return } radius = sqrt(newArea / Double.pi) } } } var myCircle = Circle(radius: 5.0) print(myCircle.diameter) // Output: 10.0 print(myCircle.area) // Output: ~78.54 myCircle.area = 100.0 print(myCircle.radius) // Output: ~5.64 print(myCircle.diameter) // Output: ~11.28 2. Methods Methods are functions defined inside the struct, encapsulating behavior tied to its data. struct Stopwatch { var seconds: Int = 0 func formattedTime() -> String { let s = seconds % 60 let m = (seconds / 60) % 60 return String(format: "%02d:%02d", m, s) } mutating func tick() { seconds += 1 } mutating func reset() { seconds = 0 } } var myStopwatch = Stopwatch() myStopwatch.tick() myStopwatch.tick() print(myStopwatch.formattedTime()) // Output: 00:02 myStopwatch.reset() print(myStopwatch

When we start programming, we quickly realize the need to organize our data and the actions we can perform on them. How can we represent a “User” with their name and email, a “Product” in an online store with price and stock, or even a simple “Point” on a screen with X and Y coordinates? We need a way to group related information into logical, reusable units.
In Swift, one of the most fundamental tools for this task is the struct. Along with class and enum, structs form the backbone of data modeling in the language. They’re far more than just “boxes” to store data—they’re powerful and versatile components.
In this post, we’ll dive into the world of structs. We’ll explore what they are, how to create them, what they can contain, and—most importantly—we’ll uncover their defining trait: being a Value Type, and why that’s such a crucial (and often advantageous) concept in Swift development. You might be surprised to find that structs are, in many cases, the preferred choice for building the foundations of your code.
What Is a Struct? The Blueprint of Data
Think of a struct as a blueprint or mold. It defines a new, custom data type by specifying which properties (data it will hold) and methods (actions it can perform) are associated with that type.
Analogy:
Imagine a house blueprint (struct House
). It defines that a house will have a number of bedrooms (bedrooms: Int
), an area (area: Double
), and perhaps a color (color: String
). It can also define actions like paint(newColor: String)
or calculateTax()
(methods). Each house built from this blueprint (let myHouse = House(...)
) is an instance of the struct.
The main goals of structs are to help us write code that is:
- Organized: Groups related data and behavior together
- Readable: Models real-world concepts or abstractions clearly
- Reusable: Defines a type that can be used across your project
Creating Your First Struct: Hands On
The syntax to define a struct is straightforward, using the struct
keyword:
struct Movie {
let title: String
var director: String
let releaseYear: Int
var userRating: Double?
var watched: Bool = false
}
To create an actual object (an instance) from this "blueprint", you can do the following:
var myFavoriteMovie = Movie(title: "Interstellar",
director: "Christopher Nolan",
releaseYear: 2014,
userRating: 9.5)
let anotherMovie = Movie(title: "Inception",
director: "Christopher Nolan",
releaseYear: 2010,
userRating: 9.2,
watched: true)
That Movie(...)
is a memberwise initializer. For structs, if you don’t define any custom init
, Swift automatically provides one that accepts an argument for each stored property (except those with default values, which are optional).
Once you have an instance, you can access its properties using dot notation:
print(myFavoriteMovie.title) // Output: Interstellar
myFavoriteMovie.watched = true
myFavoriteMovie.userRating = 10.0
print("Watched \(myFavoriteMovie.title)? \(myFavoriteMovie.watched)") // Output: true
// Trying to modify a 'let' property causes an error:
// myFavoriteMovie.releaseYear = 2015 // ERROR!
// Trying to modify any property of a 'let' instance also causes an error:
// anotherMovie.director = "Unknown" // ERROR!
What Can a Struct Contain?
Structs can include more than just stored properties.
1. Properties
Stored Properties:
Directly store a value in the instance, like title
, director
, etc.
Computed Properties:
Do not store a value but compute it based on other properties. They are always declared with var
, and you can define a get
(and optionally set
) block.
struct Circle {
var radius: Double
var diameter: Double {
return radius * 2
}
var area: Double {
get {
return Double.pi * radius * radius
}
set(newArea) {
guard newArea >= 0 else { return }
radius = sqrt(newArea / Double.pi)
}
}
}
var myCircle = Circle(radius: 5.0)
print(myCircle.diameter) // Output: 10.0
print(myCircle.area) // Output: ~78.54
myCircle.area = 100.0
print(myCircle.radius) // Output: ~5.64
print(myCircle.diameter) // Output: ~11.28
2. Methods
Methods are functions defined inside the struct, encapsulating behavior tied to its data.
struct Stopwatch {
var seconds: Int = 0
func formattedTime() -> String {
let s = seconds % 60
let m = (seconds / 60) % 60
return String(format: "%02d:%02d", m, s)
}
mutating func tick() {
seconds += 1
}
mutating func reset() {
seconds = 0
}
}
var myStopwatch = Stopwatch()
myStopwatch.tick()
myStopwatch.tick()
print(myStopwatch.formattedTime()) // Output: 00:02
myStopwatch.reset()
print(myStopwatch.seconds) // Output: 0
Why mutating
?
Because structs are value types (more on this soon). Mutating a property means conceptually replacing the whole instance. The mutating
keyword signals that the method intends to modify the struct's internal state. You can only call mutating
methods on instances declared with var
.
3. Initializers
Initializers (init
) are special methods that ensure all stored properties have an initial value. If you define a custom init
, you lose the automatic memberwise one—unless you define your custom ones in an extension.
struct RGBColor {
var red, green, blue: Double
init(r: Int, g: Int, b: Int) {
self.red = max(0.0, min(1.0, Double(r) / 255.0))
self.green = max(0.0, min(1.0, Double(g) / 255.0))
self.blue = max(0.0, min(1.0, Double(b) / 255.0))
}
init(named color: String) {
switch color.lowercased() {
case "red": self.init(r: 255, g: 0, b: 0)
case "green": self.init(r: 0, g: 255, b: 0)
case "blue": self.init(r: 0, g: 0, b: 255)
default: self.init(r: 0, g: 0, b: 0)
}
}
}
let magenta = RGBColor(r: 255, g: 0, b: 255)
print("Magenta: R=\(magenta.red), G=\(magenta.green), B=\(magenta.blue)")
let green = RGBColor(named: "Green")
print("Green: R=\(green.red), G=\(green.green), B=\(green.blue)")
The Key Trait: Structs Are VALUE TYPES
This is the most important concept about structs—and what fundamentally sets them apart from classes.
What Does It Mean to Be a Value Type?
It means that when you assign a struct instance to a new variable or pass it to a function, Swift creates a complete and independent copy of that instance.
struct Point {
var x: Int
var y: Int
}
var originalPoint = Point(x: 10, y: 20)
var copiedPoint = originalPoint
copiedPoint.x = 99
print("Original: (\(originalPoint.x), \(originalPoint.y))") // (10, 20)
print("Copy: (\(copiedPoint.x), \(copiedPoint.y))") // (99, 20)
Quick Contrast with Classes (Reference Types)
Classes are reference types. When you assign a class instance to another variable, both variables point to the same object in memory. If one changes it, the other sees the change.
Simple Analogy:
- Value Type (Struct): Like making a photocopy of a document. Two independent papers.
- Reference Type (Class): Like sharing a Google Doc. Everyone edits the same document.
Why Is This a Good Thing?
- Predictability: You know that passing a struct to a function won’t accidentally modify the original.
- Thread Safety: In multi-threaded code, working with independent copies avoids race conditions.
- Performance: Structs are stack-allocated, which is fast. Swift also uses copy-on-write to avoid unnecessary copying until needed.
When Should You Use Structs in Swift? The Golden Rule
Apple and the Swift community recommend:
Start with
struct
by default. Useclass
only when you need specific features that classes provide (like inheritance, identity, or deinitialization).
Structs are ideal for:
-
Modeling Simple Data: Values, measurements, or logical groupings where identity isn’t critical (e.g.,
CGPoint
,CGRect
,String
,Array
,Dictionary
). - Immutable or Independent Data: When each part of your code should work with its own copy of the data.
- No Need for Inheritance: Structs don’t support inheritance. If you need it, go with classes.
Structs vs. Classes: Summary of Key Differences
Feature | Struct (Value Type) | Class (Reference Type) |
---|---|---|
Copied on Assignment | Yes | No (shared reference) |
Inheritance | No | Yes |
Mutability | Needs mutating keyword |
Direct |
Stored in | Stack (usually) | Heap |
Thread Safety | Safer | Needs caution |
Default Initializer | Automatic (memberwise) | Requires manual init |
Conclusion: Building with Confidence
Structs are much more than simple containers. They are the fundamental and versatile building blocks that allow you to model your app’s data in a clean, organized, and safe way.
Their value semantics are one of Swift’s greatest strengths, promoting predictable and robust code—especially in complex or concurrent systems. By understanding and embracing structs (and the philosophy of starting with them), you’ll be writing more idiomatic, efficient, and maintainable Swift code.
Don’t be afraid to use structs. They’re your allies on the path to mastering Swift development. Try modeling the core concepts of your next project with structs and see how much cleaner and more reliable your code becomes.