Structs (short for structures) in Swift are a powerful way to define custom data types. Structs are value types, meaning that each instance holds its own copy of the data. They are lightweight and ideal for modeling simple data structures like points, shapes, and configurations. While structs are similar to classes, they differ in some important ways, especially in how they handle copying and memory management.
In this article, we’ll explore everything you need to know about structs in Swift—how to define them, work with properties and methods, and understand key differences between structs and classes. By the end, you’ll know how to use structs effectively to design robust and efficient Swift applications.
What is a Struct?
A struct in Swift is a custom data type that can contain properties and methods. Structs are used to model related data in a clean, organized way. Since structs are value types, they are copied when passed around, ensuring that changes to one instance do not affect others.
Here’s the basic syntax for defining a struct:
struct Person {
var name: String
var age: Int
}
In this example, Person
is a struct with two properties: name
(a String
) and age
(an Int
).
Creating Instances of Structs
Once you define a struct, you can create instances of it by providing initial values for its properties.
Example 1: Creating an Instance of a Struct
struct Person {
var name: String
var age: Int
}
let person = Person(name: "Alice", age: 25)
print(person.name) // Output: Alice
print(person.age) // Output: 25
In this example, we create an instance of the Person
struct by providing a name and an age.
Structs are Value Types
One of the key features of Swift structs is that they are value types. This means that when you assign a struct to a variable or pass it to a function, Swift creates a copy of the data rather than passing a reference. Changes to one copy do not affect the others.
Example 2: Structs as Value Types
struct Person {
var name: String
var age: Int
}
var person1 = Person(name: "Alice", age: 25)
var person2 = person1 // Creates a copy of person1
person2.name = "Bob"
print(person1.name) // Output: Alice
print(person2.name) // Output: Bob
In this example, modifying person2
does not affect person1
because they are separate copies. Each instance holds its own data.
Properties in Structs
Structs can have stored properties, which hold constant or variable values. You can also define computed properties, which calculate values dynamically based on other properties.
Example 3: Stored Properties in a Struct
struct Rectangle {
var width: Double
var height: Double
}
let rectangle = Rectangle(width: 10.0, height: 5.0)
print("Width: \(rectangle.width), Height: \(rectangle.height)")
Here, width
and height
are stored properties that hold the dimensions of a rectangle.
Example 4: Computed Properties in a Struct
Computed properties do not store values directly. Instead, they compute the value on demand.
struct Rectangle {
var width: Double
var height: Double
var area: Double {
return width * height
}
}
let rectangle = Rectangle(width: 10.0, height: 5.0)
print("Area: \(rectangle.area)") // Output: Area: 50.0
In this example, the area
property is computed based on the width
and height
properties. It is recalculated each time it is accessed.
Methods in Structs
Structs can also have methods, which are functions that operate on the data within the struct. These methods can be used to modify or work with the struct’s properties.
Example 5: Adding Methods to a Struct
struct Circle {
var radius: Double
func area() -> Double {
return .pi * radius * radius
}
}
let circle = Circle(radius: 3.0)
print("Area of circle: \(circle.area())") // Output: Area of circle: 28.27
Here, the area()
method calculates and returns the area of the circle based on its radius.
Mutating Methods
Since structs are value types, you cannot modify their properties directly from within an instance method. To do so, you need to mark the method as mutating, which explicitly allows it to modify the properties of the struct.
Example 6: Mutating Methods
struct Point {
var x: Double
var y: Double
mutating func moveBy(dx: Double, dy: Double) {
x += dx
y += dy
}
}
var point = Point(x: 2.0, y: 3.0)
point.moveBy(dx: 1.0, dy: -2.0)
print("New position: (\(point.x), \(point.y))") // Output: New position: (3.0, 1.0)
In this example, the moveBy
method is marked as mutating
because it modifies the x
and y
properties of the Point
struct.
Initializers in Structs
By default, Swift provides a memberwise initializer for structs, which allows you to initialize all stored properties by providing values for them. However, you can also define your own custom initializers.
Example 7: Custom Initializer in a Struct
struct Rectangle {
var width: Double
var height: Double
init(size: Double) {
self.width = size
self.height = size
}
}
let square = Rectangle(size: 5.0)
print("Width: \(square.width), Height: \(square.height)") // Output: Width: 5.0, Height: 5.0
In this example, we define a custom initializer that creates a square by setting both the width
and height
to the same value.
Structs vs. Classes
Structs and classes in Swift are similar in many ways, but they differ in some key areas:
- Value types vs. Reference types: Structs are value types, while classes are reference types. This means that structs are copied when passed around, while classes are passed by reference.
- Inheritance: Structs do not support inheritance, whereas classes do. If you need to create a type hierarchy, you must use classes.
- Memory management: Classes are managed using reference counting, which can introduce complexity with shared mutable state. Structs, on the other hand, are copied, making them simpler in terms of memory management.
Example 8: Comparing Structs and Classes
struct PersonStruct {
var name: String
}
class PersonClass {
var name: String
init(name: String) {
self.name = name
}
}
var structPerson = PersonStruct(name: "Alice")
var classPerson = PersonClass(name: "Bob")
var anotherStructPerson = structPerson
var anotherClassPerson = classPerson
anotherStructPerson.name = "Charlie"
anotherClassPerson.name = "Dave"
print(structPerson.name) // Output: Alice (struct is a value type, so a copy is made)
print(classPerson.name) // Output: Dave (class is a reference type, so the reference is shared)
In this example, modifying anotherStructPerson
does not affect structPerson
, because structs are copied. However, modifying anotherClassPerson
changes classPerson
because classes are passed by reference.
Real-World Example: Modeling a Car with a Struct
Let’s use what we’ve learned to model a simple car using a struct. We’ll define properties like make
, model
, and speed
, and add methods to accelerate and brake.
struct Car {
var make: String
var model: String
var speed: Int
mutating func accelerate(by amount: Int) {
speed += amount
print("Accelerating by \(amount) mph. Current speed: \(speed) mph.")
}
mutating func brake(by amount: Int) {
speed = max(0, speed - amount) // Speed can't go below 0
print("Braking by \(amount) mph. Current speed: \(speed) mph.")
}
}
var myCar = Car(make: "Toyota", model: "Camry", speed: 60)
myCar.accelerate(by: 20)
myCar.brake(by: 50)
In this real-world example, the Car
struct represents a car with properties for its make, model, and speed. We define two mutating methods: accelerate
to increase the speed and brake
to decrease the speed.
Best Practices for Using Structs
- Use structs for simple data: Structs are ideal for lightweight, immutable data types like coordinates, dimensions, and configurations.
- Favor structs for value semantics: If you want to ensure that changes to one instance do not affect others, use structs to take advantage of value semantics.
- Avoid inheritance: If you need inheritance
, use classes. Structs do not support subclassing, and they are best suited for flat data structures.
- Use mutating methods: When modifying a struct’s properties from within a method, mark the method as
mutating
to allow property changes. - Leverage computed properties: Use computed properties to derive values dynamically based on other stored properties without storing extra data.
Conclusion
Structs in Swift are a powerful and efficient way to define custom data types. They allow you to model data with value semantics, ensuring that each instance is independent and changes don’t affect other instances. With their support for properties, methods, and custom initializers, structs are a key tool in your Swift development toolkit.
In this article, we covered:
- Defining structs and creating instances with stored and computed properties.
- Understanding value types, where structs are copied when passed or assigned.
- Using mutating methods to modify struct properties.
- Working with custom initializers to set up structs.
- Comparing structs vs. classes to understand when to use each.
In the next article, we’ll dive into classes in Swift—how to define classes, work with inheritance, and leverage object-oriented programming concepts like reference types, subclassing, and polymorphism.
Happy coding!