Swift can infer and add sugar a lot, so why in init, it can't infer `value = value` as `self.value = value`?

struct Foo {
    let value: String

    init(_ value: String) {
        value = value          // Cannot assign to value: 'value' is a 'let' constant
        self.value = value.  // but it can't be anything but this, so why not just allow the above?
    }
}

FWIW, if Foo.value is a (settable) compute property, it could reasonably be interpreted as self.value = self.value as well.

1 Like

I don't understand what you mean:

struct Foo {
    let value: String
    // is this "settable" computed var?
    var bar: Int {
        get { _bar }
        set { _bar = newValue  }
    }
    var _bar: Int

    init(_ value: String, _ bar: Int) {
        // can we have implicit self here? so instead of this
        self.value = value
        self.bar = bar
        // can be this:
        value = value
        bar = bar
        // like if-let:
        // if let value = value
    }
}

After Foo is initialized, you can do self.bar = self.bar. Regardless, IMO, the rule you're proposing is strangely circumstantial. I have a hard time deciding whether it is a good sugar/inference or not.

1 Like

Imho that would be quite a lot of new complexity for a small gain.
A clever way to assign all members automatically could make a difference, but just getting around typing "self."?
If you really don't like the rules, you can also choose different names for either the member of the parameter, so that there is no ambiguity anymore.

2 Likes

I can appreciate the desire to not have the weird redundancy that languages often have with Class init functions. That said, value = value reads as a mistake to me.

What I'd love is a way to say "If this init declaration mentions its property symbols in the parameter list, just set them."

Maybe @prop?

Class Foo {
   let bar: Int
   let fum: Int 

  init(_ bar: @prop, fumbled: Int ) {
   self.fum = fumbled * 3
  }
}

Such that var x = Foo(2,4) yields x.bar == 2 and x.fum == 12

2 Likes

So many @ attributes for parameters already: @escaping, @someResultBuilder, @somePropertyWrapper (are there more?). Not sure about yet another one...

I feel allowing this:

value = value

is just another case of implicit self. We can have this in my example init or any member method:

var value = value    // implicit self on 2nd value

Isn't what I want the same, just implicit self apply to the first value?

The right randside is implicit the member variable because at this point there is no other local declaration of value (note that a parameter is a declaration in the function body local scope), but if you do something like this

 class Val {
  var value: Int = 0
  
  init() {
    let value = value // OK same as let value = self.value
    value = value // error: cannot assign to value: 'value' is a 'let' constant
  }
}

Because basically if you think of this in top-bottom order of declarations now at the point value = value there is a local variable value already declared which for the rules of the language takes "precedence" (note that precedence may not be the formal term here but it can get the idea across) meaning that every reference to value from this point on is choose to be the local declaration unless explicit specified with self.value. So at the end the same behavior is applied to parameter which are also local declaration in the scope of the function body. So the point is basically the compiler can infer implicit self for a member unless this member is ambiguous with another declaration in the local scope of function/initializer because if there is a local decl it will always be look-up first and reference be resolved as local variable which is a general rule of the language as far as my understanding goes :)

So basically, for value = value is not that the compiler can't infer implicit self, is the left randside will be always resolved as the local parameter declaration and this will be basically assignment to itself in case this value was an inout parameter but since all non-inout parameters are let variable for the context of the function body, we get the cannot assign to value: 'value' is a 'let' constant ...

1 Like

No. There's nothing to indicate that this is a particular case, it just looks like a bog standard assignment.
var foo = foo and for that matter let foo = foo makes it explicit that a variable is being created.

self.value = value makes it clear that two different variables are involved.

Implicit actions are fine, in my mind, only when the situation is unambiguous.

This boilerplate is annoying, but removing self., IMO, makes code less readable at the expense of marginal improvement in writing convenience.

Iā€™d be more interested in compiler-generated memberwise initializers for public structs (and maybe even classes).

5 Likes

+1

1 Like

What is this and how does it work? So this is different from synthesized init? The reason you have to hand code init because you need to do special processing. There is no way it can be just "compiler-generated".

here's my take on it. talking only about those cases when custom code is not needed, so it's only about renaming / reordering parameters and obvious compiler generated boilerplate implementation, for both structs and classes.

struct / class T {
    let x: Int
    let y: Double
    let z: String
    let w: Bool

    // examples:

    init(x, renamed y, reordered w, _ z)
    // usage: T(x: 1, renamed: 1.0, reordered: true, "z")

    init(_ x, _ y, _ z, _ w)
    // usage: T(1, 1.0, "z", true)

    // this is the default memberwise initialiser we know and love
    init(x, y, z, w)
    // usage: T(x: 1, y: 1.0, z: "z", w: true)
}

i also find it non ideal that my custom initialiser prevents the default memberwise initialiser, in order to preserve default initialiser i have to declare my initialiser in an extension which is often distracting.

1 Like

I see: full control over parameter labels for synthesized init. :+1:

yep, and you can have more than one.

i also like @Hacksaw's idea above (and just realised that mine is very similar). maybe combine the two ideas?

  init(_ x, _ y, w, string z)     // body is not needed here

  init(_ x, y, reordered w, z: String) {     // body is required here for z
     self.z = "custom" + z
  }
  // usage: T(1, y: 1.0, reordered: true, z: "z")
  
  init(x, y) // error, z/w are left uninitialised (unless they have default values in type or unless body is provided)
  
  init(x = 1, y, _ w, z = "default")    // default values also allowed here
2 Likes

Oh wow, that would be fantastic! I really wanted this! This solved this need very elegantly!

Same thing. I'm talking about cases where synthesized init would work (no special processing), but it is not synthesized because it is a public struct or class. I'm suggesting that rather than trying to improve the self.foo = foo line itself, to reduce the sheer volume of such lines.

Cases when you need to apply special processing to a few fields, but the rest are copied as is, can be mitigated by breaking down your type into smaller ones, so that special processing of selected fields is encapsulated into smaller types, and on the top level synthesized init would work.

I cannot think of any good reasons to have parameters of the initializer to be named or ordered differently from the properties. IMO, that would only introduce unneeded inconsistency. If there happen to be such reasons, they probably deserve a comment in code. In such case, I would prefer to have hand-written initializer, just to have sufficient space for a high-quality comment.

I agree it would be handy to turn default values of properties into default values of initialiser arguments. Not sure why it is not the case yet.

In the summary, I'm thinking of something like this:

public struct Foo {
    public var bar: String
    public var baz: Int = 42

    // memberwise - contextual keyword
    // compiler synthesises init(bar: String, baz: Int = 42)
    public init memberwise
}

This already works:

struct Foo {
    var bar: String
    var baz: Int = 42
}

Foo(bar: "moo")
Foo(bar: "cow", baz: 17)

Synthesized init for public structs seems to be orthogonal to the default value for parameters feature.

"I cannot think of any good reasons to have parameters of the initializer to be named or ordered differently from the properties."

Consider a case where a class has two () -> T properties, where one might be quite short, like a function name, but the other might be a multi-line closure, and you can't predict which. It sure would be nice to have more than one init ordering.

I would think such cases are rare enough that it's not too much a burden on developers to handle it themselves by defining their own init. You can even have one version simply forward to the other, effectively turning it into a one-liner.

1 Like
Terms of Service

Privacy Policy

Cookie Policy