Swift Evolution Proposal — Add typeprivate Access Level

1. Introduction

Swift currently provides several access modifiers (private, fileprivate, internal, public, open), but no modifier allows restricting visibility to a type and its extensions across files.

This proposal introduces a new access level:

typeprivate

typeprivate allows symbols to be accessed only by the declaring type (class / struct / enum) and its extensions, regardless of file boundaries. This access level provides finer encapsulation without forcing code organization into a single file.


2. Motivation

Swift uses private to restrict visibility to a declaration scope, but this is file-scoped, not type-scoped:

Modifier Same Type Same File Extension Other File Extension Other Types
private ✓ (same file)
fileprivate ✓ (same file)
internal
(missing) type type type

Problems commonly encountered:

2.1 Large types split into multiple files

Developers often split a large type into multiple files using extensions:

  • To separate protocol conformances

  • To organize functionality

  • For readability and maintainability

However, because private cannot be accessed in extensions in different files, developers are forced to:

  • Make a member fileprivate (too broad)

  • Make it internal (way too broad)

  • Merge files (hurts maintainability)

2.2 Current workarounds are inadequate

Problem with fileprivate

fileprivate gives access to other unrelated types within the same file:

fileprivate var cache: [String: User] = [:] // too visible

Problem with internal

internal allows access from entire module, exposing implementation details.

2.3 Developers want “type-only encapsulation”

There is no way to get:

private to the type, not the file.

This capability exists in languages like Kotlin (private constructor() + multi-file class) and C# (private + partial classes).

Swift's extensions are commonly used to break a type across files — but the language does not support a type-based access level.


3. Proposed Solution

Introduce a new access modifier:

typeprivate

Meaning:

A declaration is visible only within the same type (class, struct, enum) and any of its extensions, regardless of file boundaries.

3.1 Example

// File A
struct UserManager {
    typeprivate var cache: [String: User] = [:]
}

// File B
extension UserManager {
    func reset() {
        cache.removeAll()    // allowed
    }
}

// File C
struct Logger {
    func test() {
        let u = UserManager()
        u.cache   // error: 'cache' is typeprivate
    }
}


7 Likes

Have the same appeal in actual use.

Welcome to the Swift forums!

The problems that this would solve are well-known pain points in the language, and they have been discussed many times over the years (e.g. Calling private methods from extensions in separate files) but so far no consensus has been reached that would allow a proposal to move forward.

To me, a big issue with a typeprivate like this is that the "owner" or originator of the type loses control of intended privacy within the type, due to the ability of other programmers to privately extend the type in other source code without limitation. In effect, therefore, typeprivate becomes something a lot like internal, which isn't really a solution.

I recommend that you search the forums for some discussions on this subject, to get a feeling for how little agreement there is. If you can find a meaningfully different approach (or at least meaningfully different answers to the existing objections!), I'm sure the community would love to hear about that. :slight_smile:

2 Likes

Thanks for the reply, and appreciate the context.

From my own experience, the concern that the “owner of the type loses control because anyone can write an extension in another file” doesn’t quite match how access control plays out in real projects.

Only the author of the type decides whether a member becomes typeprivate.
If I believe something truly must not be accessed outside the main file, I simply keep it private. That judgment call already exists today when choosing between private, fileprivate, and internal. Adding typeprivate doesn’t weaken that—it just gives me another tool that fits a very common pattern.

Also, typeprivate is nowhere near as open as internal.
No other type gains visibility.
Only the same type and its extensions.
That’s a meaningful difference in real-world modules.

In day-to-day Swift development, we often split a type across multiple files—for protocol conformances, separation of UI logic, state handling, and so on. When that happens, private becomes “too strong” and fileprivate becomes “too weak.” The end result is usually either overexposing things with internal or merging files purely for access control, which defeats the point of Swift’s extension system in the first place.

If mutability is the concern, setters can already be restricted today:

typeprivate(set)

So the author can expose only read access if that’s their intent.

The ability for anyone to add an extension to any type already exists in Swift.
typeprivate doesn’t introduce that—it’s simply letting the type author say, “I’m okay with this member being visible to my own extensions, even across files.”

And if the author isn’t okay with that, they just don’t use it.

From my perspective, this doesn’t reduce control. It completes a gap between private and fileprivate that developers hit constantly in practice.

Happy to explore alternative boundaries or meanings too—my goal here is to solve the actual multi-file type-splitting pain point without compromising Swift’s existing privacy model.

Thanks again for the discussion.

2 Likes

fileprivate or private is by natural narrow than internal which means they can't be accessed outside of the current module.

But typeprivate may have 2 different rules: eg. For typeprivate struct A {} in module AKit.

  • Allow access AKit.A in other module within extension AKit.A { ... } scope.
  • Deny access AKit.A in other module.

IMO, typeprivate should implicitly mean internal. But we can't get such information from its name naturally.

Thanks for the comment! To clarify, in my proposal typeprivate is intended as a member-level access control modifier, not for the type itself.

Its semantics are:

  • A typeprivate member is visible only to the type and its extensions, even if they are in other files.

  • It is completely hidden from other types and from outside the module.

  • You can also combine it with typeprivate(set) if you want read-only exposure to other extensions.

This fills a common gap in Swift:

  • private → too narrow (single file only)

  • fileprivate → too weak (any code in the file)

  • typeprivate → just right for multi-file type splitting while keeping members hidden from everything else

The goal is to give type authors more precise control over member visibility across extensions, without weakening Swift’s existing privacy model.

It sounds to me like what you actually want is something more like folderprivate, or perhaps submoduleprivate.

That is, there exists some grouping of files within a module, larger than one file but smaller than the whole module, to which you want to restrict certain implementation details that are only relevant to that group of files.

1 Like

Since the motivating case is being able to split a type across multiple files within the same module, I think it makes sense to say that typeprivate is a subset of internal - that is, it acts like private outside of its originating module. That enables the motivating case while preventing abuse by inappropriate extensions in other modules.

I think that sounds good to me. Are there other concerns?

2 Likes

Slight aside - Is there a reason you left of package level access? At quick glance, it seems like it might provide what you are looking for. It isn’t as restrictive as what you propose and it won’t open things to extensions of the Type in other packages, but some of the discussion seems to say that might not be desired.

OK, apologies, I misread your example. You have the typeprivate member declared in the original type, so the keyword is not used on the type extension.

I still think the weakness in your suggestion is that typeprivate is only type-scoped, while private is file-scoped and type-scoped.

That is, currently, if the owning type author wants other people to prevent other people from messing with their private stuff, they can police this at the file level — "get your mitts off my file!".

With your typeprivate, anyone can put an extension anywhere, including buried within otherwise unrelated source files. This is the "semi-internal" behavior that I mentioned earlier, and I think it's a Bad Thing.

For this to work, I think the owner needs to be able to specify somehow which files are allowed to reference the private members in extensions of the type. This could be by (somehow) listing file names, or (as @Nevin suggests) grouping eligible files into a submodule or subfolder (somehow).

2 Likes

Well, there is a universal tool for handling things considered bad: A linter.

I’m pretty sure we won’t see any additional changes to the access levels for a long time, so it’s probably better to focus on solutions that do not need changes in Swift itself.

Thanks again for all the thoughtful comments.
After carefully reading through the discussion, I’d like to clarify the intent of my proposal and address the two main concerns raised so far.

1. Two concerns raised by the community

(1) Swift’s access control is file-based today

Some comments point out that allowing “type-only, cross-file” access would require Swift to define which files belong to a type, which could introduce complexity around:

  • file grouping,

  • declaration order,

  • submodules or folder structure.

(2) Extensions in Swift are open

Since anyone can add an extension to any type, allowing extensions to access typeprivate might weaken encapsulation because Swift does not distinguish “author-owned extensions” from “third-party extensions.”


2. Why I believe these concerns don’t apply to the feature I’m proposing

The misunderstanding largely comes from assuming that typeprivate is meant to expand fileprivate or define new file relationships.

That is not my intent.

**My proposal is much simpler:

typeprivate should be a narrower subset of internal, not a wider version of fileprivate.**

Swift’s internal already allows cross-file access by default.
typeprivate would not create new file boundaries.
Instead, it simply tightens internal:


Access Rules for typeprivate (as intended):

  • Accessible: the type itself and its extensions within the same module

  • Not accessible: any other type in the same module

  • Not accessible: anything outside the module


Under this interpretation:

(A) No new file identity mechanism is required at all

Because the feature does not attempt to determine “which files belong to the type.”

It relies entirely on the existing rule:

“Extensions are part of the type.”

Which leads to the second point…

(B) Allowing extensions to access type-scoped members is consistent with Swift’s existing model

Swift already treats extensions as part of a type’s definition:

  • adding protocol conformances

  • adding initializers

  • adding functionality

  • often placed in separate files for organization

So allowing extensions to see typeprivate is not a new risk—
it’s actually more restrictive than internal, not less.

typeprivate strengthens encapsulation by preventing all other module types from accessing the member, which internalwould allow.

It does not expand visibility.


3. Why the name should derive from the internal family (not the fileprivatefamily)

fileprivate is fundamentally about the file boundary.
My proposal explicitly tries to avoid file-based encapsulation, because:

  • file boundaries do not align well with real-world type organization

  • types frequently split across multiple files

On the other hand:

  • internal already implies “cross-file access allowed”

  • I only want to reduce that scope to “this type only”

So conceptually the feature is:

internal → (narrower) → typeinternal
not
fileprivate → (wider) → typeprivate

Thus a name like:

typeinternal

(or similar)

matches the semantics much more precisely:


4. Example

struct User {
    typeinternal var token: String = ""   // visible only to User and its extensions
}

extension User {
    func refreshToken() {
        token = "new-token"               // ✓ allowed: same type extension
    }
}

struct Service {
    func use(user: User) {
        // user.token   // ✗ not allowed: not the same type
    }
}

This expresses exactly the missing access level between private/fileprivate and internal:

“Cross-file visibility, but only within the same type.”

No new scoping rules, no new file grouping rules,
just a refinement of an existing access boundary.

2 Likes

I would include sub-types as well. My biggest need is for hiding implementation within class hierarchies spread amongst several files.

1 Like

Question 1: Will the proposal step further to add some concept like friend class in C++?.

For struct A { typeprivate var a: Int }, A can access a. But a friend of type A can also access A.a eg.

struct A {
  typeprivate var a: Int
}

@friend(A)
struct B {
  func inspect(_ a: A) {
    print(a.a)
  }
}

Question 2: For a typeprivate property in a base class, can inherited sub class access it?

eg.

class Base {
  typeprivate var a: Int
}

class D: Base {
  func inspect() {
    print(self.a) // ?
  }
}

Yes, I think it's very clear what you're proposing, and the next step would be to clarify your justification of why this (a pretty significant addition to the language) is important enough to do (and to make the language more complex for).

It's a little odd that you've ended up with typeinternal, though. I've checked what you wrote several times, and your stated primary motivation is to split large type implementations across multiple files. So, you're proposing a type-scoped access modifier to solve a file-based problem in the current Swift.

It sort of seems like a file-based problem would better to address with a file-based solution?

2 Likes

Thanks for the feedback. I’d like to clarify why typeinternal (or type-scoped access control) is necessary, and why a file-based solution like fileprivate doesn’t adequately solve the problem.


1. The limitation of fileprivate

The core motivation for this proposal is splitting large type implementations across multiple files—for example:

  • Separating UI logic, state handling, and protocol conformances into separate files

  • Keeping each file focused and maintainable

fileprivate is fundamentally file-scoped, meaning that if a member is fileprivate:

  • Any extensions in a different file cannot access it

  • To work around this, developers would have to merge multiple logical pieces into a single file, which defeats the purpose of Swift’s extension model and natural file organization

In other words, fileprivate forces type organization around files, rather than allowing types to organize their own members across files.


2. How typeinternal addresses this

typeinternal provides type-level visibility across files, while still maintaining encapsulation:

  • Accessible: the type itself and all its extensions (even across files)

  • Not accessible: other types in the same module

  • Not accessible: anything outside the module

This allows:

  • Large types to be naturally split across files

  • Members to remain private to the type

  • Modules to remain clean without exposing implementation details


3. Minimal language complexity

typeinternal does not introduce new file grouping rules, submodules, or complex visibility domains.
It is simply:

a narrower subset of internal scoped to the type, not a widening of fileprivate.

In practice, this is a natural complement to the existing access control hierarchy (private, fileprivate, internal, public) and solves a real-world pain point in large Swift projects.


4. Example: fileprivate vs typeinternal

// File 1
struct User {
    fileprivate var token: String = "" // ❌ only accessible in the same file
}

// File 2
extension User {
    func refreshToken() {
        // token = "new-token" ❌ cannot access token here
    }
}

With typeinternal:

struct User {
    typeinternal var token: String = ""   // visible only to User & extensions
}

// File 2
extension User {
    func refreshToken() {
        token = "new-token" // ✅ allowed
    }
}

struct Service {
    func use(user: User) {
        // user.token ❌ not allowed for other types
    }
}

This demonstrates clearly that file-scoped access (fileprivate) is insufficient, and that type-scoped access solves the practical problem without breaking encapsulation.


Summary

typeinternal:

  • Allows safe, cross-file type member access

  • Keeps members hidden from other types and modules

  • Solves the pain point of splitting large types naturally

  • Adds minimal complexity to Swift’s access control system

4 Likes

typeinternal will be pretty useful and extremely often need. In almost all packages I've ever created, there is at least one type that is needed to be separated to several files for verity of reasons:

  • Incapsulation, like an internal (or typeinternal, effectively) method, which call a bunch of complex private / fileprivate methods.
  • logical organization
  • reduce cognitive load
    ...

I see some parallels between package & typeinternal.
package access level help to organize and reuse code across modules of the same package. It is useful at module-level boundaries.
But there are no abilities to organize complex Types. For type-level boundary we typically use internal with underscore.

One problem I see with typeinternal is that we might want not only internal, but also typePackage or typePublic.
But this can lead to long debates and bring difficulties for pushing this pitch. That's why my suggestion is to keep typePackage or typePublic as future directions.

Something like:

public typeinternal(set) var foo: Int

typePackage var bar: Int

Once enough motivating examples gathered for typePackage or typePublic, a new pitch is done.

For now typeinternal solves typical problems of SDK and libraries development.

2 Likes