How can I make the memberwise initialiser public, by default, for structs in Swift?
Asked Answered
L

5

244

I have a Swift framework that defines a struct:

public struct CollectionTO {
    var index: Order
    var title: String
    var description: String
}

However, I can't seem to use the implicit memberwise initialiser from another project that imports the library. The error is:

'CollectionTO' cannot be initialised because it has no accessible initialisers

i.e. the default synthesized memberwise initialiser is not public.

var collection1 = CollectionTO(index: 1, title: "New Releases", description: "All the new releases")

I'm having to add my own init method like so:

public struct CollectionTO {
    var index: Order
    var title: String
    var description: String

    public init(index: Order, title: String, description: String) {
        self.index = index;
        self.title = title;
        self.description = description;
    }
}

... but is there a way to do this without explicitly defining a public init?

Lupe answered 6/10, 2014 at 20:53 Comment(0)
L
388

Quoting the manual:

"Default Memberwise Initializers for Structure Types The default memberwise initializer for a structure type is considered private if any of the structure’s stored properties are private. Otherwise, the initializer has an access level of internal.

As with the default initializer above, if you want a public structure type to be initializable with a memberwise initializer when used in another module, you must provide a public memberwise initializer yourself as part of the type’s definition."

Excerpt from "The Swift Programming Language", section "Access Control".

Lupe answered 6/10, 2014 at 21:6 Comment(11)
Well, that's annoying. :(Privatdocent
[github.com/apple/swift-evolution/blob/master/proposals/… says: “Quoting Chris Lattner: The default memberwise initializer behavior of Swift has at least these deficiencies (IMO): 2) Access control + the memberwise init often requires you to implement it yourself”. So maybe it is just a deficiency which exists for no reason in particular. Couldn’t find more references about this.Tayib
Correct me if I'm wrong. But you can still access it in your internal project, but to access it from another project then you need to manually write a public initializer...Magnum
Well it has private access, so in swift this means it is scoped to the enclosing source file (rather than the enclosing declaration). So yes, you can access it from your internal project, in the same source file.Lupe
@DanLoewenherz Yes it's inconvenient. The rationale is: “A public type defaults to having internal members, not public members. If you want a type member to be public, you must explicitly mark it as such. This requirement ensures that the public-facing API for a type is something you opt in to publishing, and avoids presenting the internal workings of a type as public API by mistake.” Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. itun.es/gb/jEUH0.lLupe
Annoying, but I understand the reason behind. Now that I'm extracting a common code containing data transfer objects in a library I have to literately add public everywhere.Babylonia
A compiler directive would be nice to override the default behaviour, and make it public.Lupe
Internal is a terrible choice for a default, imo. It basically guarantees that you will run into unexpected problems when referencing a module externally for the first time. Why not default everything to private, so that we can tell immediately when something is not given the correct access level and then decide if it should be public or internal?Nolin
All my struct's properties are public, but I still don't get a public memberwise initializer. I did declare some additional initializers in an extension, maybe that's why? I thought putting them in an extension would still let me have the default.Janniejanos
@Tayib It does appear to just be a deficiency. There's a discussion thread on Swift Evolution going on here: forums.swift.org/t/… If anyone has time to contribute an implementation, it sounds like it would be welcome!Snapp
Xcode can auto generate initialisers on structs to save some minutes. Right-click on struct's title, choose "REFACTOR" -> "GENERATE MEMBERWISE INITIALISER" -> just declare the initialiser public manually.Horsepowerhour
R
181

While it is not possible to have the default memberwise initializer at least you can make one quickly with the following steps:

UPDATE: Xcode 11 and later

As mentioned by Brock Batsell on the comments, for Xcode 11 and later all you need to is this:

  • Right click the class or struct name and choose refactor -> Generate Memberwise Initializer

Xcode 10 and earlier answer

  1. Make the object a class temporarily instead of a struct
  2. Save
  3. Right click the class name and choose refactor -> Generate Memberwise Initializer
  4. Change it back to a struct
Reina answered 10/6, 2019 at 17:19 Comment(5)
brilliant. can't thank you enough to work around this swift deficiencyImmunogenic
Works and also motivates to actually use classes instead of structs youtube.com/watch?v=_V2sBURgUBIQuandary
That's a super tip!!Composure
As of Xcode 11, you can use Generate Memberwise Initializer on structs, too!Contrasty
Fantastic!!! It's not the right answer, but is a very good tip! Helped me a lot!Dalpe
Z
2

You have to define public init by yourself, luckily starting from Xcode 14 🥳 there is an automatic initializer completion (source - 60399329)

enter image description here

Zadoc answered 9/12, 2022 at 10:12 Comment(2)
Only took them 8 years.Lupe
Please note that the init must be placed after the fields to make it work.Mcmahan
D
0

We now have a ruby gem 💎 to parse a complete swift data model file, line-by-line, and add public access modifiers, public member-wise default initializers, and other things into a separate auto-generated output swift file.

This gem is called swift_republic

Please check out the following documentation for running this gem:

https://github.com/mehul90/swift_republic

Dolerite answered 13/1, 2019 at 7:48 Comment(1)
Awesome solution!! I already created a wrapper for swift_republic and working fine.Pericope
G
0

Sometimes it's really annoying having an initializer when you don't need one. If you're constantly updating the variables to the object, it becomes bothersome very quickly to update the variables in 3 places (variable declaration, initializer parameter, and initializer implementation). A workaround I've used for this issue is to have a static variable on the struct to act as (or essentially wrap) the "initializer". For instance:

struct MyStruct {
    static var empty = Self()
    static func empty(name: String) -> Self {
        .init(privateName: name)
    }

    private var identifier: String = ""
}

Then you can call it similar to how you would an initializer (with autocomplete and everything!):

func someFunction(_ value: MyStruct) { ... }

//someFunction(.init()) -> ERROR, invalid due to `private` variable
someFunction(.empty)
someFunction(.empty(name: "Dan IRL"))
let myObject = MyStruct.empty
let myObject2 = MyStruct.empty(name: "Monty Python")
Globate answered 2/11, 2022 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.