Initialization of default values

I am working on a proposal for flexible memberwise initialization and would like to understand how Swift currently handles initialization of members that have a default specified in the declaration when an initializer does not explicitly initialize them. Can someone familiar with this provide a brief summary or point me in the right direction to learn more (either documentation or compiler code).

Thanks,
Matthew

This is under flux. Currently the compiler emits a default argument generator function for each defaulted argument, and emits a corresponding call at any call site that uses the default argument. The idea behind this was that the value of the argument could be changed without breaking ABI, but in practice this has been problematic, and constrains resilience in other undesirable ways. We're planning to move to a more C++-like model, where the default argument expression is instantiated at each call site. I think Doug and Jordan would be the best ones to discuss both the current model and our planned final design.

-Joe

···

On Dec 9, 2015, at 3:28 PM, Matthew Johnson via swift-dev <swift-dev@swift.org> wrote:

I am working on a proposal for flexible memberwise initialization and would like to understand how Swift currently handles initialization of members that have a default specified in the declaration when an initializer does not explicitly initialize them. Can someone familiar with this provide a brief summary or point me in the right direction to learn more (either documentation or compiler code).

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
    let d: Double
    let s: String = "default"
    
    // actual compiler generated signature is init(d: Double, s: String)
    init(d: Double) {
        // compiler generated initialization of s
        // self.s = s
        self.d = d
    }
    // current state compiler generated default value function
    static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Thanks,
Matthew

···

On Dec 10, 2015, at 10:28 AM, Joe Groff <jgroff@apple.com> wrote:

On Dec 9, 2015, at 3:28 PM, Matthew Johnson via swift-dev <swift-dev@swift.org> wrote:

I am working on a proposal for flexible memberwise initialization and would like to understand how Swift currently handles initialization of members that have a default specified in the declaration when an initializer does not explicitly initialize them. Can someone familiar with this provide a brief summary or point me in the right direction to learn more (either documentation or compiler code).

This is under flux. Currently the compiler emits a default argument generator function for each defaulted argument, and emits a corresponding call at any call site that uses the default argument. The idea behind this was that the value of the argument could be changed without breaking ABI, but in practice this has been problematic, and constrains resilience in other undesirable ways. We're planning to move to a more C++-like model, where the default argument expression is instantiated at each call site. I think Doug and Jordan would be the best ones to discuss both the current model and our planned final design.

-Joe

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
   let d: Double
   let s: String = "default"

   // actual compiler generated signature is init(d: Double, s: String)
   init(d: Double) {
       // compiler generated initialization of s
       // self.s = s
       self.d = d
   }
   // current state compiler generated default value function
   static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Right now, we’re doing what Joe describes for default arguments of parameters only. So if you had written:

  struct S {
    let d: Double
    let s: String

    init(d: Double, s: String = “default”) { … }
  }

  let s = S(d: 1)

the compiler would generate

  // current state compiler generated default value function
   static func sDefault() -> String { return "default" }

and call

  S(d: 1, s: sDefault())

The future state isn’t actually settled. We’re not thrilled with the idea of having to serialize expressions into Swift modules, which is what we would need to do to have the compiler turn the caller into

  S(d: 1, s: “default”)

The alternative that (IIRC) we’re currently favoring is to mark the SIL functions created as the default argument generators as “transparent”, so the SIL itself gets serialized into the Swift module and inlined into the call site (always). This is actually a smaller change to achieve the same effect, and avoids a lot of otherwise-unnecessary work to define the serialization of statements and expressions into Swift modules.

  - Doug

···

On Dec 10, 2015, at 10:13 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Doug, that makes sense. I was wondering if Joe's answer might have been referring to arguments with defaults.

What are you doing for members with default values specific (and therefore not initialized by code in an initializer)? Do you plan changes to this as well?

Matthew

···

Sent from my iPad

On Dec 11, 2015, at 5:11 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Dec 10, 2015, at 10:13 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
  let d: Double
  let s: String = "default"

  // actual compiler generated signature is init(d: Double, s: String)
  init(d: Double) {
      // compiler generated initialization of s
      // self.s = s
      self.d = d
  }
  // current state compiler generated default value function
  static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Right now, we’re doing what Joe describes for default arguments of parameters only. So if you had written:

   struct S {
     let d: Double
     let s: String

     init(d: Double, s: String = “default”) { … }
   }

   let s = S(d: 1)

the compiler would generate

// current state compiler generated default value function
  static func sDefault() -> String { return "default" }

and call

   S(d: 1, s: sDefault())

The future state isn’t actually settled. We’re not thrilled with the idea of having to serialize expressions into Swift modules, which is what we would need to do to have the compiler turn the caller into

   S(d: 1, s: “default”)

The alternative that (IIRC) we’re currently favoring is to mark the SIL functions created as the default argument generators as “transparent”, so the SIL itself gets serialized into the Swift module and inlined into the call site (always). This is actually a smaller change to achieve the same effect, and avoids a lot of otherwise-unnecessary work to define the serialization of statements and expressions into Swift modules.

   - Doug

Members with initial values get their initialization synthesized into the body of the type's initializer. I think we do this during SILGen, i.e. when we go to emit a SIL body for the ConstructorDecl AST node.

(We use the term "initial value" for "the thing on the right side of the equal sign for a variable or constant" and "default value" for "the thing on the right side of the equal side for a parameter".)

Jordan

···

On Dec 11, 2015, at 16:28 , Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Doug, that makes sense. I was wondering if Joe's answer might have been referring to arguments with defaults.

What are you doing for members with default values specific (and therefore not initialized by code in an initializer)? Do you plan changes to this as well?

Matthew

Sent from my iPad

On Dec 11, 2015, at 5:11 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Dec 10, 2015, at 10:13 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
let d: Double
let s: String = "default"

// actual compiler generated signature is init(d: Double, s: String)
init(d: Double) {
     // compiler generated initialization of s
     // self.s = s
     self.d = d
}
// current state compiler generated default value function
static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Right now, we’re doing what Joe describes for default arguments of parameters only. So if you had written:

  struct S {
    let d: Double
    let s: String

    init(d: Double, s: String = “default”) { … }
  }

  let s = S(d: 1)

the compiler would generate

// current state compiler generated default value function
static func sDefault() -> String { return "default" }

and call

  S(d: 1, s: sDefault())

The future state isn’t actually settled. We’re not thrilled with the idea of having to serialize expressions into Swift modules, which is what we would need to do to have the compiler turn the caller into

  S(d: 1, s: “default”)

The alternative that (IIRC) we’re currently favoring is to mark the SIL functions created as the default argument generators as “transparent”, so the SIL itself gets serialized into the Swift module and inlined into the call site (always). This is actually a smaller change to achieve the same effect, and avoids a lot of otherwise-unnecessary work to define the serialization of statements and expressions into Swift modules.

  - Doug

Thanks Jordan. I was guessing a synthesis like this was happening but didn't want to assume without asking.

Is the initialization of members with initial values synthesized at the beginning of the initializer body before any user code? If not, at what point in the initializer does this happen?

Also, thanks for filling me in on the terminology distinction. I'll try to remember that!

Matthew

···

Sent from my iPad

On Dec 11, 2015, at 8:26 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Members with initial values get their initialization synthesized into the body of the type's initializer. I think we do this during SILGen, i.e. when we go to emit a SIL body for the ConstructorDecl AST node.

(We use the term "initial value" for "the thing on the right side of the equal sign for a variable or constant" and "default value" for "the thing on the right side of the equal side for a parameter".)

Jordan

On Dec 11, 2015, at 16:28 , Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Doug, that makes sense. I was wondering if Joe's answer might have been referring to arguments with defaults.

What are you doing for members with default values specific (and therefore not initialized by code in an initializer)? Do you plan changes to this as well?

Matthew

Sent from my iPad

On Dec 11, 2015, at 5:11 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Dec 10, 2015, at 10:13 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
let d: Double
let s: String = "default"

// actual compiler generated signature is init(d: Double, s: String)
init(d: Double) {
    // compiler generated initialization of s
    // self.s = s
    self.d = d
}
// current state compiler generated default value function
static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Right now, we’re doing what Joe describes for default arguments of parameters only. So if you had written:

struct S {
   let d: Double
   let s: String

   init(d: Double, s: String = “default”) { … }
}

let s = S(d: 1)

the compiler would generate

// current state compiler generated default value function
static func sDefault() -> String { return "default" }

and call

S(d: 1, s: sDefault())

The future state isn’t actually settled. We’re not thrilled with the idea of having to serialize expressions into Swift modules, which is what we would need to do to have the compiler turn the caller into

S(d: 1, s: “default”)

The alternative that (IIRC) we’re currently favoring is to mark the SIL functions created as the default argument generators as “transparent”, so the SIL itself gets serialized into the Swift module and inlined into the call site (always). This is actually a smaller change to achieve the same effect, and avoids a lot of otherwise-unnecessary work to define the serialization of statements and expressions into Swift modules.

- Doug

Thanks Jordan. I was guessing a synthesis like this was happening but didn't want to assume without asking.

Is the initialization of members with initial values synthesized at the beginning of the initializer body before any user code? If not, at what point in the initializer does this happen?

At the beginning. See SILGenFunction::emitMemberInitializers.

  - Doug

···

On Dec 11, 2015, at 7:34 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Also, thanks for filling me in on the terminology distinction. I'll try to remember that!

Matthew

Sent from my iPad

On Dec 11, 2015, at 8:26 PM, Jordan Rose <jordan_rose@apple.com> wrote:

Members with initial values get their initialization synthesized into the body of the type's initializer. I think we do this during SILGen, i.e. when we go to emit a SIL body for the ConstructorDecl AST node.

(We use the term "initial value" for "the thing on the right side of the equal sign for a variable or constant" and "default value" for "the thing on the right side of the equal side for a parameter".)

Jordan

On Dec 11, 2015, at 16:28 , Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Doug, that makes sense. I was wondering if Joe's answer might have been referring to arguments with defaults.

What are you doing for members with default values specific (and therefore not initialized by code in an initializer)? Do you plan changes to this as well?

Matthew

Sent from my iPad

On Dec 11, 2015, at 5:11 PM, Douglas Gregor <dgregor@apple.com> wrote:

On Dec 10, 2015, at 10:13 AM, Matthew Johnson <matthew@anandabits.com> wrote:

Thanks Joe. It sounds like in both current state and future state the compiler synthesizes additional arguments to the initializers for the type. Is that correct?

Doug and Jordan, if you’re able to offer any additional insights that would be much appreciated as well. I want to make sure my proposal aligns well with how you’re handling this.

This is some code showing my understand of how you describe current and future state. Can you confirm if this is correct?

struct S {
let d: Double
let s: String = "default"

// actual compiler generated signature is init(d: Double, s: String)
init(d: Double) {
   // compiler generated initialization of s
   // self.s = s
   self.d = d
}
// current state compiler generated default value function
static func sDefault() -> String { return "default" }
}

// current state: actual compiler generated call is S(d: 1, s: S.sDefault())
// future state: actual compiler generated call is S(d: 1, s: "default")
let s = S(d: 1)

Right now, we’re doing what Joe describes for default arguments of parameters only. So if you had written:

struct S {
  let d: Double
  let s: String

  init(d: Double, s: String = “default”) { … }
}

let s = S(d: 1)

the compiler would generate

// current state compiler generated default value function
static func sDefault() -> String { return "default" }

and call

S(d: 1, s: sDefault())

The future state isn’t actually settled. We’re not thrilled with the idea of having to serialize expressions into Swift modules, which is what we would need to do to have the compiler turn the caller into

S(d: 1, s: “default”)

The alternative that (IIRC) we’re currently favoring is to mark the SIL functions created as the default argument generators as “transparent”, so the SIL itself gets serialized into the Swift module and inlined into the call site (always). This is actually a smaller change to achieve the same effect, and avoids a lot of otherwise-unnecessary work to define the serialization of statements and expressions into Swift modules.

- Doug