UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 11: How do memberwise initializers work when it comes to privately marked properties?

Forums > 100 Days of SwiftUI

Hi! I would like to clarify how memberwise initializers work/look like when it comes to using access control, specifically using private properties of structs. I fully understand how access control, memberwise initializers, and custom initializers work, but I'm getting a little bit stumped on why certain things behave how they do when playing around in Playgrounds.

For example, if I were to create an Order struct with only a private id property inside it, it seems obvious that I cannot create an order instance using the id property, as it is marked as private. I can only use the id property inside of the Order struct. The default memberwise initializer created for Order cannot allow for anything marked as private to be used inside of it because of this reason. Thus, an error message popping up at instantiation (as commented in the code) makes sense.

However, when I create an AnotherOrder struct with the same id property marked as private, but this time a custom init() function (simply just setting the id parameter to the id property), there is no error. I'm certain this is because when you create an instance of this struct, you're passing in a number to the id parameter, which is then sent to the actual privately marked property. Thus, you can have "indirect" write access to private properties.

After looking at these two different pieces of code, I just can't seem to understand how the memberwise initializer of the Order struct is any different than the custom one I wrote for AnotherOrder. Is the memberwise initializer any different than the one I wrote out? I'm wondering this because I'm pretty sure that the memberwise initializer is exactly the initializer I wrote out in AnotherOrder, which confuses me because if that's the case, there shouldn't actually be an error. Is id in the Order initializer a parameter or a direct reference to the id property of Order?

Please let me know if this needs more clarification. In essence, I'm really just wondering how memberwise initializers differ from the custom one I wrote. Perhaps the custom one does the same thing as a memberwise initializer but memberwise initializers work slightly differently? I don't really think that's right but... I don't know. Any help would be great! I just want to understand the little details like this! Have a great day! :)

// What's the difference between the memberwise initializer in here causing the error...

struct Order {
  private var id: Int
}

let order = Order(id: 2) // Error: 'Order' initializer is inaccessible due to 'private' protection level

// ... Compared to the custom, explicitly written initializer here?

struct AnotherOrder {
  private var id: Int
  init(id: Int) {
    self.id = id
  }
}

let anotherOrder = AnotherOrder(id: 2)

// Isn't the initializer for both of these structs the exact same? The second example just has it explicitly written out.
// If that's the case, why would an error even appear?

1      

A memberwise initiializer directly sets the values of your struct's properties. Thus, if a property is marked as private you cannot set it from outside the struct. That's why you get that error.

If you create an initializer like in your second example, you are passing in a separate parameter that you then assign to the private property inside the init. This is only confusing in your example because you have named that parameter the same as the private property.

If, instead, you were to do something like this:

struct AnotherOrder {
  private var id: Int
  init(identifier: Int) {
    self.id = identifier
  }
}

or even:

struct AnotherOrder {
  private var id: Int
  init(_ id: Int) {
    self.id = id
  }
}

you would see that what is being passed into the init is not the same thing as the private property id.

2      

I got it! To simplify everything, I think I just had small a misunderstanding of how memberwise initializers work then. They DO directly set the value of a struct's properties, which clears up why I was confused. I was writing a slightly different custom initializer! :)

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Sorry, I wasn't entirely accurate with my earlier answer so allow me to clarify.

It's not that the default memberwise initializer directly accesses the struct's properties. It's that the default memberwise initializer normally has an access level of internal but if there are private properties (without assigned default values) then the initializer also gets an access level of private. If the initializer is private you obviously can't call it from outside the struct, thus the error you are receiving.

2      

Oh! Got it. So, properties aren't "directly" accessed from the memberwise initializer call site. The actual error message makes a lot more sense now, since it describes how the actual Order initializer is inaccessible due to the private protection level, rather than my theory, which would have depicted that actual properties are inaccessible. The error message clearly talks about the entire initializer, not a portion of it.

1      

I would like to clear this up in general, if you don't mind. If I were to make a struct and mark even just one of its properties as private, I would expect to have that same error if I'm using a memberwise initializer.

For example:

struct Pear {
    private var color: String
    private var price: Double
}

let pear = Pear(color: "Green", price: 3.50) // 'Pear' initializer is inaccessible due to 'private' protection level

Based on your corrected answer, since the properties are both marked as private, that means that the memberwise initializer changes its access level from internal to private, which then obviously is the reason why it can't be called outside of the struct.

However, if I were to write:

struct Apple {
    private var color: String
    private var price: Double

    init(color: String, price: Double) {
        self.color = color
        self.price = price
    }
}

let apple = Apple(color: "Red", price: 4.25)

This code works great, without any errors. That must be because I am overriding the memberwise initializer and I am setting parameters equal to my actual properties, which (despite being private) can still be accessed indirectly.

The only difference between these two scenarios is that I am using a memberwise initializer in one struct, which seems to conform to whatever access control of the properties (the behavior seems to be that if you have just one private property, internal will be overridden) compared to a custom initializer, which is indirectly accessing the private properties.

Now the question is since the memberwise initializer gets marked as private if I have properties also privately marked, then why is my custom initializer for the Apple struct not causing any errors? That custom initializer works just like the memberwise one, except when it comes to access control apparently. So sorry if I am bombarding you with questions, I just really would love to know! :)

1      

(the behavior seems to be that if you have just one private property, internal will be overridden)

The default memberwise initializer for a structure type is considered private if any of the structure’s stored properties are private. Likewise, if any of the structure’s stored properties are file private, the initializer is file private. Otherwise, the initializer has an access level of internal. Swift Programming Language: Access Control: Default Memberwise Initializers for Structure Types

1      

Great, thank you for your help! :)

1      

Now the question is since the memberwise initializer gets marked as private if I have properties also privately marked, then why is my custom initializer for the Apple struct not causing any errors?

Sorry, I missed this question earlier...

It's because only the default memberwise initializer automatically synthesized by the compiler gets assigned the same access level as the most restrictive property; your explicit initializer gets assigned internal access level by default unless you specify otherwise. Specifically: Custom initializers can be assigned an access level less than or equal to the type that they initialize. Since you don't specify an access level for the Apple struct, it defaults to internal.

2      

Wonderful! I appreciate your help. That's what I was thinking, but it's nice to have that confirmed. I think it's all falling into place now and everything makes sense! :)

I don't know if you would know the answer to this, but is there any reason why Swift memberwise initializers are designed in such a way? What's the benefit of being able to change its access level to private after a property is changed to private? Couldn't it be designed in such a way where it could just stay internal so that I wouldn't have to write my own custom initializer to handle that?

My initial thought was that memberwise initializers obviously don't want to work with privately marked properties only to be used externally, but you can do exactly that with custom initializers. This is why I am a bit confused. That behavior is a result of the fact that you cannot make memberwise initializers with private properties, but what's the actual reason for it?

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.