Swift Custom Operators: Define and Use Your Own Symbols
Swift lets you define your own operators when built-in symbols do not express your domain clearly enough. This article explains how custom operators work, how to declare them safely, and when they are worth using.
Quick answer: In Swift, you can create custom operators with prefix, infix, or postfix declarations, then implement them as functions. You must also define precedence and associativity for infix operators so Swift can parse expressions correctly.
Difficulty: Advanced
Helpful to know first: You'll understand this better if you already know Swift functions, value types, and how built-in operators like + and == behave.
1. What Is Swift Custom Operators?
Custom operators are user-defined symbols that behave like Swift operators. They let you express a specific operation with concise syntax, such as domain math, parsing helpers, or special collection logic.
- They can be prefix, infix, or postfix.
- They are implemented with normal Swift functions.
- Infix operators need a precedence group and associativity.
- They are part of the language, not a framework feature.
Swift also supports operator overloading for existing operators, but custom operators go one step further by letting you introduce a new symbol entirely.
2. Why Swift Custom Operators Matter
Custom operators matter when your code benefits from concise, readable notation for a specific domain. They are common in libraries that model math, graphics, parsing, or composable transformations.
They are less useful for everyday application logic where explicit method names are often clearer. A custom operator should improve meaning, not hide it.
3. Basic Syntax or Core Idea
Swift custom operators have two parts: a declaration and an implementation.
Declare the operator
First, tell Swift that a new symbol exists. For an infix operator, you also choose a precedence group.
precedencegroup VectorAdditionPrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator +~: VectorAdditionPrecedenceThe precedence group tells Swift how to group expressions that mix your operator with others. The operator declaration makes the symbol available to the compiler.
Implement the operator
Then write a function with the same symbol as its name.
struct Vector2D {
var x: Double
var y: Double
}
func +~ (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
return Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}Now the operator can be used just like a built-in symbol.
4. Step-by-Step Examples
Example 1: A custom infix operator for vectors
This example adds two vectors using a domain-specific symbol. It is useful when the meaning is obvious in the context of geometry or simulation code.
struct Vector2D {
var x: Double
var y: Double
}
precedencegroup VectorAdditionPrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator +~: VectorAdditionPrecedence
func +~ (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}
let start = Vector2D(x: 2, y: 3)
let delta = Vector2D(x: 5, y: 1)
let result = start +~ deltaThis code creates a readable expression for vector addition. The result is a new vector with each component added independently.
Example 2: A prefix operator for unary negation
Prefix operators appear before their operand. They are good for unary operations such as sign inversion or custom transformations.
prefix operator ~>
prefix func ~> (value: Int) -> Int {
-value
}
let original = 12
let flipped = ~>originalThis prefix operator returns the negative of an integer. Even though this example is simple, the same pattern works for more specialized unary behavior.
Example 3: A postfix operator for a custom check
Postfix operators come after the operand. They are less common, but Swift supports them when they make the domain clearer.
postfix operator ?
postfix func ? (value: Int) -> Bool {
value % 2 == 0
}
let isEven = 8?Here the operator returns true for even numbers. This is syntactically valid, but it is also a good reminder that not every clever operator is a good API choice.
Example 4: A custom operator with a protocol extension
Custom operators are especially useful when multiple types can support the same behavior. A protocol extension can centralize the implementation.
protocol Scalable {
func scaled(by factor: Double) -> Self
}
prefix operator ^^
prefix func ^^ <T: Scalable> (value: T) -> T {
value.scaled(by: 2)
}
struct Money: Scalable {
var amount: Double
func scaled(by factor: Double) -> Money {
Money(amount: amount * factor)
}
}
let salary = Money(amount: 1200)
let doubleSalary = ^^salaryThis example shows that operators can be generic and reusable, not just attached to one concrete type.
5. Practical Use Cases
- Vector, matrix, and geometry code where symbolic notation mirrors the underlying math.
- Parsing and transformation pipelines where a short symbol represents a domain-specific operation.
- State combination or merging logic in libraries that want compact expressions.
- Functional-style APIs that compose transformations in a concise way.
- DSL-like code where a symbol makes a custom meaning easier to scan than a long method name.
For application UI code, a normal method like merge(with:) or clamped(to:) is often easier to maintain than a custom operator.
6. Common Mistakes
Mistake 1: Declaring an operator but forgetting its implementation
Declaring the symbol is not enough. Swift still needs a function body that tells it what the operator does.
Problem: The compiler knows the operator exists, but there is no matching function to execute when you use it.
infix operator +~
let a = 1
let b = 2
let sum = a +~ bFix: Add the operator function with matching parameter types.
infix operator +~
func +~ (lhs: Int, rhs: Int) -> Int {
lhs + rhs
}
let sum = 1 +~ 2The corrected version works because Swift can now resolve the symbol to an actual function.
Mistake 2: Using an infix operator without a precedence group
Infix operators need precedence rules so Swift can parse expressions consistently. Without them, expressions can become ambiguous or fail to compile.
Problem: Swift cannot safely determine how your custom operator should group with other operators.
infix operator <~>
func <~> (lhs: Int, rhs: Int) -> Int {
lhs + rhs
}
let value = 2 <~> 3 + 4Fix: Define a precedence group and assign the operator to it.
precedencegroup CustomMathPrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator <~>: CustomMathPrecedence
func <~> (lhs: Int, rhs: Int) -> Int {
lhs + rhs
}
let value = 2 <~> 3 + 4The corrected version gives Swift enough information to parse the expression the way you intend.
Mistake 3: Choosing a symbol that makes the code unclear
A custom operator can technically compile and still be a poor design choice if nobody can read it later.
Problem: The operator hides intent, so the code becomes harder to understand and maintain than a named method would be.
infix operator %%
func %% (lhs: String, rhs: String) -> String {
lhs + " " + rhs
}
let message = "Hello" %% "world"Fix: Use a descriptive method when the symbol does not add real clarity.
extension String {
func joinedWithSpace(_ other: String) -> String {
self + " " + other
}
}
let message = "Hello".joinedWithSpace("world")The corrected version is easier to read because the intent is explicit instead of symbolic.
7. Best Practices
Use operators only when the domain already suggests symbolic notation
If the operation looks mathematical or strongly compositional, a custom operator can be a good fit. Otherwise, prefer a named function.
// Good fit: expression mirrors the domain.
let distance = pointA +~ pointB
// Often clearer as a named function.
let formatted = formatTitle(text)This keeps the API readable for future contributors.
Choose a precedence group deliberately
Do not rely on guesswork. Decide how your operator interacts with arithmetic, comparison, or logical expressions.
precedencegroup PipelinePrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator |>: PipelinePrecedenceClear precedence avoids surprising grouping in complex expressions.
Keep operator behavior small and predictable
An operator should do one obvious thing. If it performs multiple steps, hides side effects, or depends on global state, it becomes hard to trust.
// Prefer a simple, deterministic operation.
func +~ (lhs: Vector2D, rhs: Vector2D) -> Vector2D {
Vector2D(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
}Predictable behavior makes the operator safe to reuse in many contexts.
8. Limitations and Edge Cases
- Custom operators can make code harder to understand if the symbol is not obvious.
- They are available to all code in scope, so naming collisions can happen in large codebases.
- Infix operators need precedence groups; prefix and postfix operators do not.
- You cannot redefine how Swift parses the operator beyond the precedence system.
- Operators may compile but still create confusing APIs when used in public libraries.
Warning: Be careful exposing custom operators in shared frameworks. Once an operator becomes part of a public API, removing or renaming it is a breaking change for downstream users.
9. Practical Mini Project
Let us build a tiny geometry helper that uses a custom operator to translate a point by a vector. This example shows the full flow from declaration to use.
struct Point {
var x: Double
var y: Double
}
struct Offset {
var dx: Double
var dy: Double
}
precedencegroup TranslationPrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator >+<: TranslationPrecedence
func >+< (point: Point, offset: Offset) -> Point {
Point(x: point.x + offset.dx, y: point.y + offset.dy)
}
let origin = Point(x: 10, y: 20)
let shift = Offset(dx: 3, dy: -4)
let moved = origin >+< shiftIn this mini project, the operator makes the operation read like a compact domain statement. The complete example works because the type declarations, operator declaration, and operator function all match.
10. Key Points
- Custom operators let you create new symbols for domain-specific behavior.
- Swift supports prefix, infix, and postfix operators.
- Infix operators need a precedence group and implementation.
- Use them only when the symbol improves readability.
- Public operators should be designed carefully because they become part of your API surface.
11. Practice Exercise
- Create a Fraction type with numerator and denominator.
- Define a custom infix operator that adds two fractions.
- Choose a precedence group so the operator behaves predictably in expressions.
- Test it with two sample values and print the result.
Expected output: A new fraction representing the sum of the two inputs.
Hint: Start by implementing fraction addition with a normal function, then move the same logic into a custom operator.
struct Fraction {
var numerator: Int
var denominator: Int
}
precedencegroup FractionAdditionPrecedence {
associativity: Left
higherThan: AdditionPrecedence
}
infix operator +÷: FractionAdditionPrecedence
func +÷ (lhs: Fraction, rhs: Fraction) -> Fraction {
let numerator = lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator
let denominator = lhs.denominator * rhs.denominator
return Fraction(numerator: numerator, denominator: denominator)
}
let first = Fraction(numerator: 1, denominator: 2)
let second = Fraction(numerator: 1, denominator: 3)
let total = first +÷ second
print(total)12. Final Summary
Swift custom operators let you introduce your own symbols for operations that fit your domain better than a long method name. They are powerful because they integrate into Swift syntax, but that same power makes them easy to overuse.
The safest approach is to reserve custom operators for code where the symbol is immediately meaningful, the behavior is small and predictable, and the precedence rules are clear. When those conditions are met, operators can make a specialized API elegant and expressive.
If you want to go further, the next best step is to study operator overloading on existing symbols such as +, ==, and < so you can compare when a custom symbol is better than extending a familiar one.