[Proposal] 'selfprivate' Access Control - allowing access in extension within '`self`'

I'd like to suggest the addition of a new Access Control: 'selfprivate' - the name provided isn't required, just following 'fileprivate' convention.

The reason for this is to allow building projects in which we can use an extension in another file and provide access to source from this other file. Advantages would be to improve access safety.

First file:

// File example: ClientSecurity.swift
internal final class ClientSecurityStuff {
	private init() {}
	internal static let singleton = ClientSecurityStuff()
	/// #USAGE: ⤹⤹ '`selfprivate`'proposal.
	internal selfprivate(set) var isAvailable: Bool = false
	// ...
}

Second file:

// File example: DownloadSecurity.swift
fileprivate final class ServerSecurityStuff {
	fileprivate func downloadFromServer() {
		ClientSecurityStuff.singleton.updateAvailability(true)
		// ....
	}
	// ...
}

extension ClientSecurityStuff {
	fileprivate func updateAvailability(_ isAvail: Bool) {
		self.isAvailable = isAvail
	}
}

I should mention I seen some other posts regarding access from extensions in another file. Which can be perceived differently than wanting to intentionally add more security.

Thank you in advance.

This has been previously proposed as typeprivate.

Hey - thanks for that. I've read a bunch before posting and that one from 2020, starts off as similar for intent. Though, I noticed it was getting lost into naming conventions, etc. I also did not see any solid direction (yea or nay) contributed in the thread.

Are you aware if this proposal (any related post) has made any impact for progress?

Thanks!

The Language Workgroup is the authoritative voice here, but I would expect there is little to no appetite for changes to Swift’s access control story at this point in time.

Thank you for the info. Let's hope their appetite is piqued soon.

This has also been discussed here

In short, I don't think it's a good idea. Among other things, it would allow to work around privateness in extensions, by adding members that refer to the internal selfprivate member:

extension ClientSecurityStuff {
  var isAvailableWorkaround: Bool {
    get { self.isAvailable }
    set { self.isAvailable = newValue }
  }
}

I don't get this argument. How is that different from adding members that refer to private members? or adding public members that refer to internal members?

-thanks for sharing your thoughts...

@tclementdev I agree with you here.

@ExFalsoQuodlibet I don't see how that would be the fix. Mainly, using a 'selfprivate', would only affect what is being declared as such, therefore it would have to be intentional.

Additionally, my example was not meant to be exhaustive and therefore (of course), there are work-arounds for some uses.

As this thread is already showing, this is being discussed (meaning a demand). I have discovered developers wanting this for variety or reasons (file bloat, easy access, more safety, etc.). I feel adding this can solve most of that. I have yet to see a valid reason why it should not, rather at best some speculation on best practices on (non-public) conventions. Therefore, I would hope this makes movement ahead - thanks!

I would prefer that private types are available to extensions in other files within the same module. That seems simpler and gives the desired result without adding any new keywords.

@phoneyDev I’ve seen that requested by others too. In many cases your point would work out. Though, there are often situations in which you do not want to expand access in extensions. I feel this request benefits larger modules or teams, and especially valuable for sensitive things.

I’ve wanted this many times, and so far I’m not aware of any downsides to having such a concept in the language. I would love to know if anyone knows of any downsides to this

1 Like

The difference is that, to do what you say, you would need to be in the same file, which would introduce a much higher bar for accepting a change: for example, you could enforce more restrictive rules for certain files or folders in a code review.

My opinion is simply that files and folders are much more practically useful boundaries for access control, because they are based on actual "physical" concepts. By extension, I think it's safer and better to make a codebase more modular, with actual Swift modules, than to introduce complex access control rules: as I stated elsewhere, I'd still find the concept of folderprivate pretty useful though.

If we add a new typeprivate or selfprivate as mentioned by the original poster then it's a non-problem. private, internal and public still behave the same, we're not changing that.

2 Likes

@ExFalsoQuodlibet - I was able to track your logic in the beginning but then you lost me on that last part - from this paragraph of yours:

My opinion is simply that files and folders are much more practically useful boundaries for access control, because they are based on actual "physical" concepts. By extension, I think it's safer and better to make a codebase more modular, with actual Swift modules, than to introduce complex access control rules: as I stated elsewhere, I'd still find the concept of folderprivate pretty useful though.

... I'm curious, please help me understand how a folder Access Control is [more] practical. I mean that is (compiler) further away from a 'self' and file access - I get it that some may like that for ease of coding, though I feel that is not safer. I've seen ideas thrown around for 'moduleprivate' but isn't that just similar to 'internal' for a framework anyhow.

P.S. I still think having an option for a source scope private along with a lexical scope has functionality that allows for cleaner and less code; while providing the safety wanted.

There are quite a few complexities when using modules in small teams / projects:

  • quote heavy setup.
  • extra noise due to extra import statements.
  • app may accidentally have the same symbol name as in the module - compiler won't warn.
  • extra noise for "Module." prefixing in case compiler can tell there's a conflict.
  • unless when compiler doesn't (at which time "Reset Package Caches" Xcode's awkward workaround comes handy).
  • this one is quite bad: struct's autogenerated initializers are internal → hence can't be used from outside → lot's of boilerplate to add in the module.
  • modules or not - using file boundaries for access control might be in conflict with other rules in the team about where to put file boundaries.

So when it comes to a situation when the new code needs to use something private / fileprivate, and there's a choice of:

  1. "I'll keep this thing private / fileprivate and add my code into the same file".
  2. "I'll just make this thing internal instead of private / fileprivate".
  3. "I'll introduce modules to the app that previously didn't use modules, and make this thing internal to that module".

then modules are literally the last thing I'd like to do, and typically I'd resort to either 1 or 2 -- and it's always with great dissatisfaction → hence we have this topic surface every now and then, asking for a more lightweight way of a better access control rules compared to what we have today.

Partial list of related proposals and discussions over the years.

Nov 2016 https://forums.swift.org/t/swift-evolution-proposal-introduce-typeprivate-access-control-level/

Dec 2016 https://forums.swift.org/t/any-consideration-for-directoryprivate-as-a-compliment-to-fileprivate/

Feb 2017 https://forums.swift.org/t/discussion-final-lazy-fileprivate-modifiers/

Feb 2017 https://forums.swift.org/t/a-comprehensive-rethink-of-access-levels-in-swift/

Apr 2017 https://forums.swift.org/t/type-based-private-access-within-a-file/

Oct 2020 https://forums.swift.org/t/implement-alvarez-monteiros-typeprivate-possibly-with-one-or-two-extra-restrictions/

Dec 2021 https://forums.swift.org/t/pitch-new-access-control-for-access-inside-type-from-other-files/

Dec 2021 https://forums.swift.org/t/calling-private-methods-from-extensions-in-separate-files/

June 2022 https://forums.swift.org/t/introduce-type-private-access-level/

Aug 2022 https://forums.swift.org/t/proposal-selfprivate-access-control-allowing-access-in-extension-within-self/

2 Likes

Yes... we always do 1 or 2, never 3. We are also dissatisfied, but grit our teeth and move on.

1 Like

@tera - thanks for the support and gathering up that list of links!

I too, don't like resorting option #3 (in your list). In support of this thread here, and regarding the initial example I provided, there is another alternate practice you can use for properties (what I meant earlier by not being exhaustive in my example)...

First File:

// File example: ClientSecurity.swift
internal final class ClientSecurityStuff {
	private init() {}
	internal static let singleton = ClientSecurityStuff()
	/// #USAGE: ⤹⤹ '`selfprivate`'proposal.
	/* internal selfprivate(set) var isAvailable: Bool = false */
	// ...
}

Second File:

// File example: DownloadSecurity.swift
fileprivate final class ServerSecurityStuff {
	private init() {}
	// made into singleton for this alternate example
	internal static let singleton = ServerSecurityStuff()
	// alternate for this situation (if this can be a shared or singleton instance), though not ideal
	fileprivate var storeIsAvailable: Bool = false
	fileprivate func downloadFromServer() {
		ClientSecurityStuff.singleton.updateAvailability(true)
		// ....
	}
	// ...
}

extension ClientSecurityStuff {
	/// The work-around, by planting a property into '`ServerSecurityStuff`'.
	internal fileprivate(set) var isAvailable: Bool {
		get { ServerSecurityStuff.singleton.storeIsAvailable }
		set { ServerSecurityStuff.singleton.storeIsAvailable = newValue }
	}
	// keeping original method (now superfluous)
	fileprivate func updateAvailability(_ isAvail: Bool) {
		self.isAvailable = isAvail
	}
}

... which is not ideal. I would consider this alternate between your #1 & #2.

I am in favour of a variant inspired by C++ friends, where type lists the "invited guest list" of parties who would have a greater access, like so:

-- file1 --
class Foo {
	var x: Int = 0
	private func foo() {}

	components A, B // list of invited guests
}

-- the same or a different file --
component Foo.A { // like extension, but
	func bar() {
		foo() // can do this
	}
	var y: Int = 0 // and this
}

but I never saw a serious support for this or other typeprivate variant on this forum.

I think it comes down to the fact that we don't use classes to organize code, that is, we don't define classes like _Service or _Manager to just "bundle up" some stuff, and we have a functional style where data types and functions are not encapsulated in a single scope. Thus, our actual access boundary is usually the file, or the module, with some exceptions (we must still write UIKit classes, that will have private members).

So, folderprivate would nicely sit between fileprivate and "moduleprivate" (a.k.a. internal).

We also make very heavy usage of Swift modules (that is, to be precise, Swift library targets in a single Swift package) to modularize our codebase, and we found it to be great both for having a more fine-grained access to type members by using public, and for helping us with the architecture of the project in general, because it forces you to define in a more clear way what's visible outside of a module.

In general, I'd also say that this approach really helps us with PR reviews: files and folders are what we really check in a code review, and it's very clear that a PR that doesn't touch certain files or folders will not produce certain unwanted changes. Also, it helps that GitHub CODEOWNERS is based on files and folders.

1 Like

+0 from me. I don't see the harm technically. But it does seem to invite weird designs conceptually. I'd personally never use it as I design types in the spirit of the Open-Closed Principle, making them open for extension (literally here) but closed against modification. typeprivate would open a weird space there ...