When should one use swift extensions? [how to avoid over using it?]

This is somehow philosophical questions, but I hope you chose to bear with me...
I was introduced to extensions concept for the first time when I started learing swift.
although some similar ideas exists in other languages I have used, I never experienced such an extensive usage of this concept before. sometimes I found it's being used in really simple scenarios like the following:

consider the next code in targetA:

public struct Card {
   public var title: String { "My card"}

}

then in targetB the next code:

extension Card: SpecialCard {
    public var subTitle: String { "Not your card" }
}


public protocol SpecialCard {
    public var subTitle: String {get}
}

and this is the code that confused me horribly:

public someFunc(card: Card){
    print(card.subTitle)
}

at first glance I thought I am tripping. how Card has subTitle attribute?
later I managed to find the extension in the targetB.
I immediately thought it is an abuse of this concept. but I couldn't rationalize it to my peers.
do you have any framework in which you limit the use of extensions?
is there some common sense to when a developer consider it necessary, and when it is too much?

no.

seriously, in my opinion extensions are one of the most valuable features of swift. the original type declaration block should contain its stored properties, a single memberwise init, and ideally nothing else.

i think there is a lot of swift advice ("don’t use class types!", "everything should be immutable!", etc) that gets taken way too far. but so far i have not found extremism in defense of extension blocks to be any vice at all…

10 Likes

any thought about how it is not straight forward to find where the new attribute came from?
any thought about where should extensions be declared in regards to the class it extends?
to me it feels like a jungle. with conventional object oriented tools, I at least could see the relations with my eyes

1 Like

presumably, in the same file as the original type declaration, if it is an extension in the same module.

if it is an extension in a different module, it should go in a dedicated file just for that extended type.

2 Likes

Command clicking in Xcode should bring you to the relevant place, does it not?

1 Like

and what will prevent any developer just extending the class on will, ending up having multiple extensions all over the place ( different targets and locations etc..).
I am not saying I am against it, I am just trying to come up with some boundaries, do you agree there should be any?

I don't think there should be any boundaries other than perhaps some company's guidelines or coding conventions.

Extensions in Swift are similar in effect to free standing functions in other languages. You can declare:

void foo(FILE* file) { ... }

Just the calling syntax is different:

foo(file) // in C instead of
file.foo() // in Swift

You could have those anywhere and you could end up "having multiple extensions all over the place ( different targets and locations etc..)". Yes.

I don’t disagree with your sentiment, but there are a lot of “that’s what C does” arguments that fall flat.

Honestly this particular “issue” is largely moot with modern-day IDEs easily showing you all of a Type’s members (and, for that matter, where they are defined).

1 Like

thanks for the reply,
on personal note, I guess since I came from "Java" and "JS" it feels hacky to me.
although the concept is not completely foreign to those languages as well - I do have some hesitation using it so much

you should not have extensions “all over the place”, you should organize them into appropriately-named files. one system that i have gotten tremendous mileage out of is having one type per file, and appending the string (ext) to files that only contain extensions. so you could have:

- Sources/
    - ModuleA/
        - Car.swift
        - Truck.swift
        - Int (ext).swift
    - ModuleB/
        - Bus.swift
        - Car (ext).swift
        - Int (ext).swift

of course, what works for me might not work for everyone. but i find this system makes it a lot easier to remember where everything is, even in addition to IDE tools like ctrl-click to definition, etc.

2 Likes

Thanks alot.
I was looking for some guiding like this.
in many cases, I saw extension for one class reside in in a different class, just because the last one “consumes” it

Those guidelines would be very team specific. I saw teams that place all files into a single giant folder. Also saw the opposite extreme. Saw naming conventions like Car+Extensions.swift with all extensions in a single file. And the opposite `Car_methodName.swift" with individual files per a single extension method. Swift doesn't prescript a single right way here, it's all up to you and your team.

2 Likes

This is what I do, though I don’t just put a general +Extensions but rather what I’m extending it for, usually a protocol. e.g. Int+MyProtocol.swift

3 Likes

If it's my code it should be in the file with the original object's definition.
If it's extending something from an external source (Apple, 3rd party, etc.) then it should be in an appropriately named file. (As suggested above.)
I prefer one file per base object. If I have a ton of extensions on the same type, then maybe if the file becomes too long I would break it up into functionality-subnamed files.
And usually put all the extensions in a common folder.
My goal is to make the structure and form of the source code more easily visible. It should not be a "discoverable" task via right-click and searches. That is how to look up something specific. This is not Myst.

1 Like

My philosophy recently shifted away from protocols-and-extensions-should-be-declared-next-to-their-related-objects-when-possible. Now I do agree about keeping them in their own files. Mostly for protocols. But one reason this helps with extensions too is because some extensions can be pretty cursed to deal with. For example some objective C properties and classes show up as open in Swift and can actually be overridden :smirk: with extensions. What I find is that when such delicate scenarios exist, they can be very obscured in diffs on pull requests. Unless one has pretty intimate knowledge about the file it can be very easily missed, and that can be source breaking if missed. I find this can happen very easily with protocols too where a protocol is declared, and then a couple simple adopters are declared, and the file basically has multiple definitions of the same variables and func. It’s very easy for a situation where someone accidentally modifies a protocol requirement while trying to to modify an adopters implementation. Depending on how exposed the code is, if it’s overlooked in the diffs because it blended in with the actual implementation, it could be nearly invisible and hard to detect until too late.

but generally if I’m doing something bad like overriding with an extension, I put it in the same file where I did the bad

1 Like

thanks,
but if you have an access to the original object's definition, why would you like to extend it? why not just simply add the functionality to the object's class?

The advantage that uniquely defines Swift extensions is the better discoverability for those extended APIs. But extended APIs also share the same "namespace" (or name lookup scope) with other members in the same type. You don't know when there is an intruder from another framework that has the same name to your extended API in the same type. In this case, the ambiguity is raised when the programmer is trying to access it.

By looking backward for almost 10 years in Swift's history, you could find that the Swift programmers should extend API to a particular type in C++'s style -- by using freestanding functions.

From my point of view, if you are creating Swift macros, which may collaborate with particular frameworks, freestanding function is a better choice because it introduces no ambiguity by accessing with prefixed module name like "ModuleName.functionName(...)".

One reason for that is when adding a protocol conformance it makes sense to organize that into its own file for clarity.

I didn't have the chance to use swift 10 years ago, but doesn't freestanding function have different capabilities then extensions?? . can freestanding functions directly access or modify the internal properties?

Freestanding function cannot directly access private members in the body of a type's or an extension's declaration.