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. For infix operators, you should also assign a precedence group, which sets precedence and associativity. Without one, the operator gets DefaultPrecedence, and mixing it with other operators or chaining it fails to compile unless you add parentheses.

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 can be assigned a precedence group that sets their precedence and associativity; without one they use DefaultPrecedence.
  • 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

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.

Swift
precedencegroup VectorAdditionPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
}

infix operator +~: VectorAdditionPrecedence

The 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.

Swift
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.

Swift
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 +~ delta

This 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.

Swift
prefix operator ~>

prefix func ~> (value: Int) -> Int {
    -value
}

let original = 12
let flipped = ~>original

This 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.

Swift
postfix operator %?

postfix func %? (value: Int) -> Bool {
    value % 2 == 0
}

let isEven = 8%?

Here the operator returns true for even numbers. The symbol cannot be a bare ?: Swift disallows postfix operators that start with ? or ! because they would collide with optional unwrapping. Even when an operator like this compiles, it is a good reminder that not every clever operator is a good API choice.

Example 4: A generic custom operator constrained to a protocol

Custom operators are especially useful when multiple types can support the same behavior. A generic operator function constrained to a protocol can centralize the implementation.

Swift
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 = ^^salary

This 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.

Swift
infix operator +~

let a = 1
let b = 2
let sum = a +~ b
Compiler output✓ Tested · Swift 6.4
error: cannot find operator '+~' in scope

Fix: Add the operator function with matching parameter types.

Swift
infix operator +~

func +~ (lhs: Int, rhs: Int) -> Int {
    lhs + rhs
}

let sum = 1 +~ 2

The 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.

Swift
infix operator <~>

func <~> (lhs: Int, rhs: Int) -> Int {
    lhs + rhs
}

let value = 2 <~> 3 + 4
Compiler output✓ Tested · Swift 6.4
error: adjacent operators are in unordered precedence groups 'DefaultPrecedence' and 'AdditionPrecedence'

Fix: Define a precedence group and assign the operator to it.

Swift
precedencegroup CustomMathPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
}

infix operator <~>: CustomMathPrecedence

func <~> (lhs: Int, rhs: Int) -> Int {
    lhs + rhs
}

let value = 2 <~> 3 + 4

The 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.

Swift
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.

Swift
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.

Swift
// 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.

Swift
precedencegroup PipelinePrecedence {
    associativity: left
    higherThan: AdditionPrecedence
}

infix operator |>: PipelinePrecedence

Clear 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.

Swift
// 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 without a precedence group fall back to DefaultPrecedence, which is non-associative and unordered relative to arithmetic and comparison operators, so mixing or chaining them requires parentheses. Prefix and postfix operators cannot declare a precedence group at all.
  • 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.

Swift
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 >+< shift

In 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.
  • Every operator needs an implementation. Give infix operators a precedence group as well, because without one they cannot be mixed with other operators or chained without parentheses.
  • 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.

Show solution
Swift
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)
Output✓ Tested · Swift 6.4
Fraction(numerator: 5, denominator: 6)

Where AI Coding Assistants Get This Wrong

Patterns AI tools commonly produce for this topic, and what to use instead.

Often suggested: Emits pre-Swift 3 syntax such as infix operator ** { associativity right precedence 160 }, or capitalized associativity: Left.

Instead: Declare a precedencegroup with lowercase associativity: right and higherThan: / lowerThan: relations, then write infix operator **: ExponentiationPrecedence.

Swift 6.4 rejects both: "operator should no longer be declared with body; use a precedence group instead" and "expected 'none', 'left', or 'right' after 'associativity'".

Often suggested: Writes public infix operator ... in a library, or declares postfix operator ? or prefix operator &.

Instead: Declare the operator without an access modifier and mark the implementing static func or global func public; pick a symbol that doesn't collide with ?, !, &, = or ->.

These are compile errors ("unexpected attribute 'public' in operator declaration", "postfix operator names starting with '?' or '!' are disallowed"), and operator declarations are already visible to every importing module.

Often suggested: Defines ** with MultiplicationPrecedence or a left-associative custom group.

Instead: Give exponentiation its own group with associativity: right and higherThan: MultiplicationPrecedence.

Only that combination gives the mathematical results 2 ** 3 ** 2 == 512 and 2 * 3 ** 2 == 18 (checked with Swift 6.4).

Check Your Understanding

Pick an answer for each question, then check your score.

1. Which kinds of custom operators can Swift define?
Show answer

Answer: Prefix, infix, and postfix. For example -x is prefix, a + b is infix, and x! is postfix.

2. What do you write before implementing a new infix operator?
Show answer

Answer: An operator declaration such as infix operator <>: AdditionPrecedence. The declaration introduces the symbol, and the precedence group decides how it combines with other operators.

3. When is a custom operator a good idea?
Show answer

Answer: Only when the symbol makes code clearer, for example in math-like domains. A mysterious symbol is harder to read and search for than a clearly named function.

4. Why should you be careful about making a custom operator public?
Show answer

Answer: It becomes part of your library's API, visible to every user. Once others depend on it, changing or removing it is a breaking change.

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.