How to Implement Sendable Conformance for Custom Types in Swift: 7 Essential Steps

To implement Sendable conformance for custom types in Swift, ensure all stored properties are themselves Sendable for immutable value types, apply @unchecked Sendable only when manually guaranteeing thread-safety for reference types, and isolate non-Sendable members using @MainActor or thread-safe containers.

Swift 6 (and Swift 5.7 with concurrency warnings enabled) enforces strict data-race safety through the Sendable protocol, which marks types safe to transfer across actor boundaries. The Dimillian/Skills repository documents these patterns extensively in its Swift Concurrency Expert skill, providing practical guidance for safely adopting strict concurrency checking in modern Swift code.

What is Sendable and When Do You Need It?

Sendable is a marker protocol introduced in Swift 6 (back-ported to Swift 5.7) that tells the compiler a type can safely cross concurrency domains—such as being passed to Task.detached, stored in actor-isolated properties, or captured by @Sendable closures—without causing data races.

According to swift-concurrency-expert/SKILL.md, you must implement Sendable conformance whenever your custom type moves between isolation boundaries, including closure arguments, async function parameters, and actor-level properties. The compiler verifies this by checking that every stored property is itself Sendable, or that you explicitly assert safety with @unchecked Sendable.

Step-by-Step Implementation Guide

The following seven-step process, derived from the concurrency documentation in the Dimillian/Skills repository, ensures your types meet Swift's strict concurrency requirements.

1. Identify Concurrency Boundaries

First, locate where your type crosses actor or task boundaries. As noted in swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md, these boundaries occur when passing values to Task.detached, storing properties in actors, or capturing variables in @Sendable closures. These are the exact locations where the compiler validates Sendable conformance.

2. Prefer Immutable Value Types

Declare all stored properties as let constants to leverage Swift's automatic Sendable inference. Immutable value types are inherently thread-safe because they cannot be mutated after creation, making them automatically Sendable when composed of Sendable members.

3. Verify Sendable Members

Ensure every property type already conforms to Sendable. Standard library types like String, Int, and Array where Element: Sendable already conform. For custom property types, you must verify they are also Sendable compliant before the compiler will accept your conformance.

4. Add Explicit Conformance

When your type consists only of Sendable properties, declare explicit conformance:

struct Point: Sendable {
    let x: Double
    let y: Double
}

The compiler automatically accepts this conformance because Double is Sendable and the struct is immutable.

5. Resolve Non-Sendable Properties

For properties that are not Sendable—such as mutable collections or reference-type objects—choose one of these approaches:

6. Use @unchecked Sendable as Last Resort

For reference types where you can manually prove thread-safety (such as immutable objects never mutated after creation), use @unchecked Sendable:

final class Counter: @unchecked Sendable {
    var value: Int = 0
}

Only apply this when you can guarantee the instance will never be mutated from multiple threads simultaneously. The file swift-concurrency-expert/references/swift-6-2-concurrency.md demonstrates how improper use leads to compile-time errors.

7. Verify with Compiler Flags

Build with -warn-concurrency to catch hidden races. This flag surfaces violations that might pass initial compilation but create runtime data races, ensuring your implementation is complete.

Practical Implementation Patterns

The Dimillian/Skills repository provides concrete patterns for common scenarios encountered when implementing Sendable conformance.

Automatic Conformance for Structs

Immutable structs composed of Sendable types receive automatic compiler validation:

// ✅ Simple immutable struct – automatically Sendable
struct Point: Sendable {
    let x: Double
    let y: Double
}

Isolating Reference Types

When you cannot make a class truly immutable, isolate it to the Main Actor:

// ✅ Use a Sendable wrapper around a non‑Sendable reference
struct CounterBox: Sendable {
    @MainActor let counter: Counter
}

This pattern from swift-concurrency-expert/references/approachable-concurrency.md allows the struct to be Sendable while the wrapped reference remains confined to the main actor.

Crossing Concurrency Boundaries

When passing custom types to detached tasks, the compiler enforces Sendable:

func fetchData<Model: Sendable>(_ model: Model) async {
    Task.detached {
        // `model` is safe to use here because it is Sendable
        await process(model)
    }
}

Similarly, when using withTaskGroup, all captured values must conform:

func loadImages(urls: [URL]) async {
    await withTaskGroup(of: Void.self) { group in
        for url in urls {
            group.addTask {
                // `url` is a Sendable value type
                let data = try await fetchImage(at: url)
                await MainActor.run {
                    imageView.image = UIImage(data: data)
                }
            }
        }
    }
}

Summary

  • Immutable value types with Sendable properties automatically satisfy Sendable conformance without special handling
  • Reference types require @unchecked Sendable only when you can manually guarantee thread-safety, or must be isolated to actors using @MainActor
  • Non-Sendable properties should be replaced with Sendable alternatives or wrapped in thread-safe containers
  • Compiler verification using -warn-concurrency catches hidden data races before runtime
  • The Dimillian/Skills repository documents these patterns in swift-concurrency-expert/SKILL.md and related reference files to help implement strict concurrency checking correctly

Frequently Asked Questions

What is the difference between Sendable and @unchecked Sendable?

Sendable requires the compiler to verify that all stored properties are themselves Sendable, ensuring type safety through static analysis. @unchecked Sendable bypasses these compiler checks, placing the responsibility on the developer to guarantee thread-safety manually. According to swift-concurrency-expert/references/swift-6-2-concurrency.md, you should only use @unchecked when you can prove the type is never mutated across concurrency domains, such as immutable reference types.

When does Swift automatically infer Sendable conformance?

The compiler automatically infers Sendable conformance for public structs and enums when all stored properties are Sendable and the type is declared in the same module where conformance is needed. For internal and private types, Swift automatically synthesizes conformance when all members are Sendable. However, you must explicitly declare Sendable conformance for public types or when crossing module boundaries to ensure ABI stability.

How do I handle non-Sendable properties in a custom type?

When a property type cannot conform to Sendable (such as NSLock or mutable Objective-C classes), you have three options: replace the property with a Sendable alternative (e.g., Actor instead of NSLock), wrap the property in an actor-isolated container using @MainActor, or refactor your architecture to keep the non-Sendable property within a single concurrency domain. The swift-concurrency-expert/references/approachable-concurrency.md file recommends preferring value types and actor isolation over @unchecked Sendable for these cases.

Does Swift 6 require Sendable for all concurrency boundaries?

Yes, Swift 6 enforces Sendable requirements at all concurrency boundaries, including passing values to Task.detached, capturing values in @Sendable closures, and storing properties in actors. The swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md documentation explains that SwiftUI heavily utilizes these patterns, making Sendable conformance essential for modern Swift development. Build with -warn-concurrency in Swift 5.7+ to prepare for these requirements.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →