# Swift Modules and Imports: Using import and @_exported

## Pro Summary

An `import` makes another module's `public`/`open` declarations visible, but at scale the details matter. Since Swift 6.0 (SE-0409) imports take access levels (`public`, `package`, `internal`, `fileprivate`, `private import`), and an `internal import` keeps a dependency's types out of your public and `@inlinable` API. A bare `import` is still `public` in the Swift 6 language mode unless you enable the `InternalImportsByDefault` upcoming feature. Re-exporting is a separate thing: only `@_exported import` makes a dependency visible to your clients, and `public import` does not.

```
import Foundation
import struct Foundation.Date     // scoped import: only this declaration
internal import CoreModels        // SE-0409, Swift 6.0+
@testable import CoreModels       // module must be built with -enable-testing
@_exported import CoreModels      // re-export to clients (underscored)
// Logger::Level                  // module selector, Swift 6.3+ (SE-0491)
```

Requires: Access-level imports: Swift 6.0+ (SE-0409). `MemberImportVisibility` upcoming feature: Swift 6.1+ (SE-0444). Module selectors (`Module::Name`): Swift 6.3+ (SE-0491).

Gotchas:

- Extension members leak across files: if any file in a module imports Foundation, other files in that module can call `"a".localizedCaseInsensitiveCompare(_:)` without importing it (top-level types such as `Date` do not leak); `-enable-upcoming-feature MemberImportVisibility` (SE-0444) makes member lookup respect each file's own imports.
- If a module contains a type with the module's own name (module `Logger` exporting `struct Logger`), `Logger.Level` resolves against the type and fails to compile; Swift 6.3+ module selectors (`Logger::Level`) disambiguate.
- A scoped import such as `import struct Logger.Level` imports only that declaration, not the rest of the module's API.
- `@testable import X` fails with "module 'X' was not compiled for testing" unless X was built with `-enable-testing`; SwiftPM passes that flag in debug builds but not with `-c release`.
- The Swift 6.4 compiler warns that `@_implementationOnly import` is deprecated in favor of `internal import`, and that without library evolution its safety is not checked.

Primary sources:

- [SE-0409: Access-level modifiers on import declarations](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0409-access-level-on-imports.md)
- [SE-0444: Member import visibility](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md)
- [SE-0491: Module selectors for name disambiguation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0491-module-selectors.md)
- [The Swift Programming Language: Import Declaration](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/#Import-Declaration)

Source: https://devdocs10.com/articles/swift/swift-modules-imports  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift uses modules to organize code into reusable units, and the `import` keyword is how one file gains access to declarations from another module. In this article, you will learn what a Swift module is, how normal imports work, what `@_exported` does, when re-exporting is useful, and what common problems appear when modules are structured poorly.

> **Quick answer:** Use `import` to make another module's public declarations available in the current file. Use `@_exported import` only when you intentionally want one module to re-export another module so downstream files can see it automatically; it is underscored because it is not part of Swift's stable public language surface.

> **Difficulty:** Intermediate
>
> **Helpful to know first:** You'll understand this better if you know basic Swift syntax, access control like `public` and `internal`, and how frameworks or Swift packages group source files.

## 1. What Is a Swift Module?

A module is a single unit of code distribution in Swift. When you build an app target, framework target, or Swift package product, Swift treats that compiled unit as a module.

- A module groups related types, functions, protocols, and extensions.
- Each Swift file belongs to exactly one module at compile time.
- Code in one module cannot automatically see code from another module.
- You use `import` to access declarations from a different module.
- Access control matters: importing a module does not override rules like `private`, `internal`, or `public`.

Common examples of modules include:

- The Swift standard library, available automatically.
- Apple frameworks such as `Foundation`.
- Your own framework targets.
- Swift package modules such as `Networking` or `CoreModels`.

It helps to think of a module boundary as both an organization tool and a visibility boundary. This is why modules matter for architecture, API design, compile times, and dependency management.

## 2. Why Modules and Imports Matter

Modules are not just a packaging feature. They affect how large Swift projects stay maintainable.

- **Separation of concerns:** networking code, business logic, and UI-related code can live in different modules.
- **Safer APIs:** only declarations marked with appropriate access levels cross module boundaries.
- **Reuse:** one framework or package can be shared across multiple apps or targets.
- **Cleaner dependencies:** imports make it clear which external code a file depends on.
- **Scalability:** module boundaries help teams reason about large codebases.

You should use imports whenever a file depends on another module's API. You should not use re-exporting casually, because it can hide real dependencies and make module relationships less obvious.

## 3. Basic Syntax

The normal import form is simple: place `import` near the top of the file, followed by the module name.

### Importing a module

In this example, the file imports `Foundation` so it can use types such as `Date`.

```swift
import Foundation

let now = Date()
print(now)
```

Without importing `Foundation`, `Date` would not be available in this file.

### What import actually changes

Importing a module does not copy code into your file. It tells the compiler that this file may use declarations exposed by that module.

- If a type is `public` or otherwise visible across the module boundary, you may use it.
- If a type is `internal` to that other module, importing does not make it visible.
- Import is file-based in practice: one file importing a module does not automatically import it for every other file. One exception is extension members, such as the `String` methods Foundation adds: once any file in a module imports Foundation, those members are visible in the module's other files unless the `MemberImportVisibility` upcoming feature (SE-0444, Swift 6.1+) is enabled.

### Using @_exported import

Swift also supports an underscored attribute form that re-exports another module:

```swift
@_exported import Foundation
```

When this appears in a module, files that import your module may gain access to `Foundation` symbols without writing their own `import Foundation`. This is convenient in some wrapper modules, but it is more fragile because `@_exported` is underscored and not considered stable source-level API.

## 4. Step-by-Step Examples

### Example 1: Importing a framework in one file

This example shows the most common use: bringing a framework into scope for one file.

```swift
import Foundation

let names = ["Ana", "Ben", "Chris"]
let sortedNames = names.sorted { lhs, rhs in
    lhs.localizedCaseInsensitiveCompare(rhs) == .orderedAscending
}

print(sortedNames)
```

Output (tested with Swift 6.4):

```text
["Ana", "Ben", "Chris"]
```

The comparison method comes from `Foundation`. The import makes that API available in this file.

### Example 2: Importing your own module

Suppose one package module named `CoreModels` exposes a public type. Another module can import it and use it.

```swift
// In module CoreModels
public struct User {
    public let id: Int
    public let name: String

    public init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}
```

Now another module can import it:

```swift
import CoreModels

let user = User(id: 1, name: "Mina")
print(user.name)
```

This works because both the type and its initializer are `public`.

### Example 3: Import does not bypass access control

This is one of the most important module rules. Even after import, non-public declarations stay hidden across modules.

```swift
// In module Helpers
struct InternalFormatter {
    func format(_ value: String) -> String {
        "*** \(value) ***"
    }
}
```

In another module:

```swift
import Helpers

// This will not compile because InternalFormatter is not public.
// let formatter = InternalFormatter()
```

The import succeeds, but the symbol is still unavailable because its access level does not allow cross-module use.

### Example 4: Re-exporting a dependency with @_exported

Suppose you have a wrapper module named `AppSupport` that always exposes types from `CoreModels`.

```swift
// In module AppSupport
@_exported import CoreModels

public struct Session {
    public let currentUser: User

    public init(currentUser: User) {
        self.currentUser = currentUser
    }
}
```

Then a consumer may only import `AppSupport`:

```swift
import AppSupport

let user = User(id: 42, name: "Ravi")
let session = Session(currentUser: user)

print(session.currentUser.name)
```

This can make downstream code shorter, but it also hides that `User` really comes from another dependency.

## 5. Practical Use Cases

- Splitting a large app into feature modules such as authentication, networking, persistence, and shared models.
- Creating a reusable framework with a clear public API for multiple apps.
- Building a facade module that wraps lower-level modules behind a simpler interface.
- Using `@_exported` in an internal platform layer where many consumers always need the same shared dependency.
- Keeping test helpers in a separate module so production targets are not forced to depend on testing utilities.

## 6. Common Mistakes

### Mistake 1: Assuming import makes every declaration visible

Beginners often think that importing a module grants access to everything inside it. In Swift, visibility still depends on access control.

**Problem:** This code tries to use a type that is `internal` in another module, so the compiler cannot expose it across the module boundary.

```swift
// In module Utilities
struct TokenBuilder {
    func build() -> String {
        "abc123"
    }
}

// In another module
import Utilities

let builder = TokenBuilder()
```

**Fix:** Mark the type and the members you want to expose as `public`.

```swift
// In module Utilities
public struct TokenBuilder {
    public init() {}

    public func build() -> String {
        "abc123"
    }
}

// In another module
import Utilities

let builder = TokenBuilder()
```

The corrected version works because the type and initializer are now visible outside their defining module.

### Mistake 2: Forgetting to import the module in each file that needs it

Imports are normally written per file. Importing a module in one file does not automatically make it available everywhere else in your target.

**Problem:** This file uses `Date` without importing `Foundation`, leading to a compile error such as “cannot find 'Date' in scope”.

```swift
// Missing import Foundation

let createdAt = Date()
```

**Fix:** Import the required module in the file that uses its declarations.

```swift
import Foundation

let createdAt = Date()
```

The corrected version works because the compiler now knows where `Date` is defined.

### Mistake 3: Using @_exported as if it were a normal stable language feature

Because `@_exported` is convenient, it can be tempting to use it everywhere. That is risky, especially in public libraries or long-lived package APIs.

**Problem:** This module re-exports dependencies broadly, which can hide real module relationships and tie consumers to an underscored implementation detail.

```swift
@_exported import Foundation
@_exported import CoreModels
@_exported import Networking
```

**Fix:** Prefer explicit imports in consumer files, and use `@_exported` only in carefully chosen wrapper modules where re-exporting is intentional and documented.

```swift
// In the wrapper module, keep re-exports minimal or avoid them entirely.
import CoreModels

public struct Session {
    public let userID: Int

    public init(userID: Int) {
        self.userID = userID
    }
}

// In consumer files, be explicit about dependencies.
import CoreModels
import AppSupport
```

The corrected version is clearer because each file shows its actual dependencies directly.

### Mistake 4: Confusing “No such module” with a Swift syntax issue

When Swift reports `No such module`, the problem is usually not the `import` keyword itself. The build system cannot find or link the module.

**Problem:** This code imports a module that is not added to the target, package manifest, or build configuration, so the compiler cannot resolve it.

```swift
import CoreModels
```

**Fix:** Add the dependency to the target or package correctly, then rebuild. In Swift Package Manager, that means declaring the dependency and attaching the product to the target.

```swift
// Package.swift excerpt
// products: [.library(name: "CoreModels", targets: ["CoreModels"])]
// dependencies: [...]
// target dependencies: ["CoreModels"]
```

The corrected setup works because the compiler can now locate and build the imported module.

## 7. Best Practices

### Practice 1: Keep imports explicit in most application code

Explicit imports make dependencies easy to trace. This helps maintenance, code review, and refactoring.

```swift
// Less preferred when overused through re-exports
import AppSupport

// Preferred when the file directly depends on these modules
import Foundation
import CoreModels
import AppSupport
```

This approach makes it obvious which APIs the file truly depends on.

### Practice 2: Design small public surfaces across module boundaries

The more symbols you expose publicly, the harder your module is to evolve safely. Keep helper types internal unless they are part of the intended API.

```swift
// Less preferred: everything exposed
public struct JSONParser {}
public struct RawRequestState {}
public struct InternalRetryTracker {}

// Preferred: only the real API is public
public struct JSONParser {}
struct InternalRetryTracker {}
```

This keeps module boundaries cleaner and reduces accidental coupling.

### Practice 3: Use @_exported only for intentional facade modules

If you decide to re-export, do it because the module is deliberately acting as a facade or platform layer, not just to save typing.

```swift
// Reasonable: one carefully documented facade module
@_exported import CoreModels

public enum AppAPI {
    public static let version = "1.0"
}
```

This is much safer than scattering many re-exports across unrelated modules.

## 8. Limitations and Edge Cases

- `@_exported` is underscored, which means it is not treated like a stable public language feature in the same way as ordinary Swift syntax.
- Importing a module does not expose declarations that remain `internal`, `fileprivate`, or `private`.
- If two imported modules expose the same type name, you may need module qualification to avoid ambiguity.
- A file can compile with fewer explicit imports when re-exports are present, but that convenience can make real dependencies harder to see.
- `No such module` errors usually come from package, target, or build settings problems rather than Swift syntax mistakes.
- Some APIs seem available automatically because of umbrella imports or surrounding project setup, but relying on that can make code less portable to other targets or packages.

## 9. Practical Mini Project

Here is a small example of how modules and imports can be organized in a realistic package layout. Imagine three modules:

- `CoreModels` defines shared data types.
- `UserFeatures` uses those data types.
- `AppFacade` re-exports `CoreModels` and exposes higher-level functionality.

The code below shows a simple version of that structure.

```swift
// Module: CoreModels
public struct User {
    public let id: Int
    public let name: String

    public init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
}

// Module: UserFeatures
import CoreModels

public struct UserGreeter {
    public init() {}

    public func greeting(for user: User) -> String {
        "Hello, \(user.name)!"
    }
}

// Module: AppFacade
@_exported import CoreModels
import UserFeatures

public struct WelcomeService {
    private let greeter = UserGreeter()

    public init() {}

    public func message(for user: User) -> String {
        greeter.greeting(for: user)
    }
}

// Consumer module
import AppFacade

let user = User(id: 7, name: "Leah")
let service = WelcomeService()

print(service.message(for: user))
```

This mini project shows both normal imports and a deliberate re-export. In a real project, you would document clearly that `AppFacade` re-exports `CoreModels`, because consumers may otherwise not realize where `User` originates.

## 10. Key Points

- A Swift module is a compiled unit of code such as a framework, app target, or package module.
- Use `import` to access another module's visible declarations in a file.
- Importing a module does not bypass access control rules.
- `@_exported import` re-exports a module so downstream imports may see it automatically.
- `@_exported` is convenient but should be used carefully because it is underscored and can hide dependencies.
- Errors like `No such module` usually indicate dependency or build configuration problems.

## 11. Practice Exercise

Try this exercise to reinforce the difference between importing and re-exporting.

- Create a module named `SharedTypes` with a public `Product` struct.
- Create a second module named `StoreKitLite` that uses `Product`.
- First, make the consumer import both modules explicitly.
- Then, change `StoreKitLite` to re-export `SharedTypes` with `@_exported`.
- Observe how the consumer code changes.

**Expected output:** A printed product description such as `Featured: Keyboard`.

**Hint:** Make sure the struct, initializer, and used properties are `public`, or the second module will not be able to expose them across module boundaries.

```swift
// Module: SharedTypes
public struct Product {
    public let name: String

    public init(name: String) {
        self.name = name
    }
}

// Module: StoreKitLite
@_exported import SharedTypes

public struct StoreFront {
    public init() {}

    public func featuredText(for product: Product) -> String {
        "Featured: \(product.name)"
    }
}

// Consumer
import StoreKitLite

let product = Product(name: "Keyboard")
let store = StoreFront()

print(store.featuredText(for: product))
```

## 12. Final Summary

Swift modules are the language's core tool for organizing and sharing code across targets, packages, and frameworks. The `import` keyword tells the compiler which external module a file depends on, while access control decides which declarations are actually visible. That distinction is essential: importing a module gives you access only to the API that module intentionally exposes.

You also saw that `@_exported import` can re-export a dependency through another module. That can be useful in a facade or wrapper layer, but it should be used carefully because it hides direct dependencies and relies on an underscored feature. In most codebases, explicit imports are the safer default. As a next step, study Swift access control and Swift Package Manager target dependencies, because both are tightly connected to how modules behave in real projects.

## Where AI Coding Assistants Get This Wrong

- Often suggested: Uses `public import CoreModels` in a facade module and expects clients that write `import AppFacade` to see `User` automatically.
  Instead: `public import` only allows `CoreModels` types to appear in the facade's public API. Clients still need their own `import CoreModels` unless the facade uses `@_exported import` (an underscored feature). (Tested with Swift 6.4: a client of a facade that uses `public import` gets "cannot find 'User' in scope", and the same client compiles once the facade uses `@_exported import`.)
- Often suggested: Recommends `@_implementationOnly import` to hide a dependency from clients.
  Instead: Use `internal import Dependency` (SE-0409, Swift 6.0+). The compiler then rejects any use of the dependency's types in public or `@inlinable` declarations. (`@_implementationOnly` is deprecated and warns in Swift 6.4, while `internal import` is the supported, checked replacement.)
- Often suggested: Claims the Swift 6 language mode makes plain `import` internal by default, so migrating will break every public API that mentions an imported type.
  Instead: In Swift 6 mode a bare `import` is still `public`. Imports become internal by default only with `-enable-upcoming-feature InternalImportsByDefault` (`.enableUpcomingFeature("InternalImportsByDefault")`), and with that on you mark the ones you need as `public import`. (Tested with `-swift-version 6`: a public function that returns an imported type compiles with a bare import and fails only when the upcoming feature is enabled.)
