Possibility to makes classes/structs partial as C# language

Introduction

In swift we are able to add extension of classes that allows us to add methods but we cannot add attributes, properties.
C# language allows developers to make its classes partial. It is possible to split the definition of class, struct or interface over two or more source files.
Could we make swift able to do this stuff ?

Proposed solution

Add partial keyword to alert the compiler that this class is splited over one or more source files.

Example

  // Common logic for bank model
  partial class Bank {

    var amount: Int

    init() {
        amount = 0
        super.init()
    }

    func withdraw(amount amount: Int) {
        self.amount -= amount
    }

  }

  // Business logic for customer within bank model
  partial class Bank {

    unowned let owner: Customer

    init(customer: Customer) {
        self.owner = customer
    }

    func notifyCustomer() {
        // some code
    }

  }

Alternative solution

We also could add more feature with the extension paradigm. Adding properties, attributes and more directly on the extension scope.

1 Like

This feature is somewhat related to mixins/traits which have come up before.

Sadly the search functionality of this forum doesn't handle old imported posts from the mailing list very well it seems (search for "mixin" - the thread is split into many separate posts) but here is the relevant previous draft proposal I could find for reference:

1 Like

I’ve long wished for same-module extensions of structs and classes to allow stored properties.

This is (I think?) orthogonal to mixins, and a much simpler design problem. It would allow internal namespacing within a large type that would like to break its implementation into more manageable pieces while still presenting a single public API surface.

IIRC, @Slava_Pestov indicated at one point that this is a tractable problem. (Slava, sorry for putting words in your mouth if I misremember that.)

4 Likes

I think this would be the right approach for Swift, but should be an opt-in feature for a type. It’s incredibly valuable to be able to look at a declaration and know that there are not stored properties added elsewhere. I wouldn’t want to give that up as the default.

1 Like

If we allow stored properties in extensions, I think restricting it to the same file is the way to go. That would allow, eg, extensions that provide protocol conformance, to include the properties required by that protocol.

2 Likes

It’s definitely tractable. I think the main difficulty is the compiler will need to find all stored properties added via extensions even in non-WMO mode, but we already have to bind all extensions anyway.

My main concerns are about semantics - what does this mean for the synthesized memberwise initializer and DI? If we require that stored properties in extensions have an initial value it makes things a bit easier.

3 Likes

I think this case is even easier and I would call it an almost-starter-bug.

It also gets easier if this is an opt-in feature. IMO it would be acceptable to say that when you opt-in to stored properties in extensions you opt out of memberwise initializer synthesis. The use cases I know of for stored properties in extensions all involve classes which don’t support memberwise initialization anyway.

5 Likes

That’s a good point — especially since the primary use case I'm thinking of is to have the stored properties be extension-private, which would preclude their use in initializers.

I can imagine reasonable cases where one would want an initializer to set the value of a public property declared in an extension, but ruling that out doesn’t seem too painful, and could be an incremental addition later.

I disagree. I'm thinking of a few problem children where:

  • A type was overly large, but it still needed a single API surface, i.e. splitting it into smaller types wasn't an option.
  • Splitting the type across files for readability / reasonability was a good solution, but…
  • a few stored properties used exclusively by one extension still have to be on the main type, where they are (1) internal instead of private, and (2) totally separated from where they're used.

Being able to keep a stored property private to a file/extension separate from the main type is the goal here. Think “like a mixin, but only applied to one type.”

This is something where good tooling would be more helpful than an extra keyword. We already having the situation that you never know whether you’re looking at the whole type, and need a bird’s-eye view of all the extensions merged to really understand what you're looking at. Is it worth the cost of a new keyword for the special case of stored properties? Why shouldn't that keyword apply to any other type that has extensions?

3 Likes

These are all cases that I consider to be the domain of submodules. The way I envision it, a submodule is a “logical file” composed of multiple physical files.

Thus, for instance, the fileprivate keyword would actually mean “logical-file private”, hence “submodule-private”. Similarly, “only same-file extensions can add stored properties” would really mean “only extensions in the same logical-file (aka. submodule) can add stored properties”.

I am not convinced that allowing stored properties in extensions is desirable at all, but if we do go that route then I strongly believe we should not allow them outside the submodule. Therefore, until we get our submodule story finalized, I am opposed to allowing them anywhere outside the same file.

A lot of people also want all stored properties to be declared in one place, even going as far as to say they should all be at the top or bottom of a type. Loosening this restriction is going to be a controversial change regardless of implementation effort.

(I'm in favor, at least for classes.)

5 Likes

Having comprehensive knowledge of the state stored by a type is extremely important. It is so important that many people prefer to write their struct declarations to only contain the stored properties, with all behavior moved to extensions. This style was one of the motivations for modifying the semantics of private to be visible to same-type extensions in the same file. Tooling is not remotely an adequate solution here. I want to be able to see this when reading source code regardless of where that source code exists.

Given the choice between keeping things as they are and allowing any extension in the declaring module of a struct or class to add new stored properties I would strongly prefer to keep things as they are. I’m not fundamentally opposed to a feature allowing stored properties in extensions but I am strongly opposed to it being the default.

2 Likes

A project/type particularly concerned with its codable representation, or following a “pure functional” style, or particularly concerned about memory layout might well want to constrain how and where types declare state, sure. This sounds like a job for SwiftLint.

These are not merely stylistic concerns. As an opinionated language, Swift can certainly preferentially accommodate projects that are concerned with one or more of Codable representation, functional style, memory use, and stored state over projects that are unconcerned with any of the above. Indeed, if Swift could not be opinionated about this, it could hardly be opinionated about much at all.

There are going to be lots of people who have a different opinion. A proposal that insists on allowing stored properties in extensions by default is going to face a lot of resistance where a proposal with a different design may not. It’s obviously up to proposal authors to decide what design they propose, but it’s worth being aware of an issue like this. If your preferred design is the one that is pursued, at the end of the day the core team will have a judgment call to make.

Yes, I can see there’s a lot of reflexive resistance to this idea. And I appreciate it; I’m just skeptical of whether an additional keyword really addresses those concerns. It seems like some don’t like the idea of spreading state across files at all, in any form.

How do you envision an extra keyword such as partial helping in your own situation? Is the idea simply to let a team say “we never use partial,” or is it something subtler than that?

It would be a significant aid to understanding because it makes it immediately clear when reading code whether or not additional state is present. For example, when doing code review in a web interface it would call immediate attention to the fact that this feature is in use. This would be especially helpful if somebody added partial to an existing type that previously was not partial.

I can imagine cases where this feature could be useful (all in the context of classes) so I would not oppose it or ban it on my team. But I do think using it well requires more careful exercise of good judgement than many features do. That is why I believe it should not be the default and that it warrants having special attention called to it. There is a loose analogy with try and the proposed await in terms of locality (of control flow or stored state) being the default and non-locality (of control flow or stored state) being marked so readers are aware of it.

5 Likes

Perhaps starting with just allowing the “same file” case would simplify things here and cover a lot of use cases (e.g. grouping properties that exist solely or primary to implement a protocol conformance with the same-file extension declaring that conformance). I personally have no interest in seeing another keyword here, and would find it somewhat inconsistent with all the other extensions possible (computed properties, methods, etc). There are a lot of assertions here about the importance of being able to see the stored properties in once place, and how this is supposedly valuable to a lot of people, but I don't find them convincing without specific examples.

I think a great example of this is UITableViewDelegate and UITableViewDatasource conformance on iOS. I often find I need to maintain some state around these methods, e.g. the currently-selected row. It would be nice if I could declare that state in an extension with the conformance, because it's not used by the rest of the class.

(We can argue that those conformances should be on separate types, but Xcode templates still add them to the view controller, so that's still the most-common place they are implemented.)

5 Likes

Would conditionally active extensions be allowed to contribute stored properties? For wrapping Sequences I write that can be Collections when the base is, but I want to cache the startIndex and/or endIndex (for BidirectionalCollection support), this could be helpful.