Proposal: Intermediate mutation qualifier for protocol functions on reference-types

Currently, when a reference-type adopts a protocol with a function declared
as `mutating`, the reference-type's implementation cannot call that
function internally. This is because the compiler enforces an immutable
`self` pointer value, and the `mutating` qualifier implies that the
function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where a
reference-type implementation of these `mutating` functions may only want
to mutate properties owned by `self`, but not the actual `self` pointer
value.

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }

    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member
on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData`
does not mutate the `self` pointer value, reference-type implementations
are unable to call that function internally, since it is marked as
`mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by
objects which own instances of the reference-type (even though, again, it
may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to
call the sample `addRandomData` implementation internally is to qualify the
whole `RandomDataTransformable` protocol as `class`. The downside here is
that this takes an otherwise perfectly reference- and struct-compatible
protocol + extension implementation and restricts it to only apply to
classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that
applies when protocols are adopted by reference-types. The qualifier would
specify that the `self` pointer value itself may not be mutated, but
`self`'s properties may be, as appropriate.

Thoughts, feedback on this?

1 Like

A couple of miscellaneous points:

* This problem only occurs when the class inherits a default
  implementation of the method from a protocol extension. If the class
  declares the method itself, then it's no longer a mutating method and
  everything works fine.
* The problem exists because mutating functions are allowed to assign
  to self, but methods on class types cannot assign to self, they can
  only mutate the properties of self. This is why we cannot simply
  allow the call to the inherited mutating method, as that method may
  reassign self.
* Classes can still call the method, they just have to say
  something like

var this = self this.callMutatingMethod()

Yeah it's a little awkward, but it's not really all that bad for an edge
case like this.

Another potential workaround requires more work on the protocol side but
allows implementations to not care about the difference, which is to
provide a non-mutating variant in an extension restricted by Self:
AnyObject:

protocol P { var count: Int { get set } mutating func foo()
-> String }

extension P { mutating func foo() -> String { return
_mutatingFoo(&self) } }

extension P where Self: AnyObject { func foo() -> String { var
this = self return _mutatingFoo(&this) } }

private func _mutatingFoo<T: P>(inout value: T) -> String {
value.count += 1 return "foo" }

Ultimately, I think there's some value in this proposal, but I worry
about adding a new keyword to handle an edge case like this when there's
workarounds available. Assuming we do add this feature, I'd suggest
using something like `mutating(ish)` instead of inventing a brand new
keyword (there's precedent in `private(set)`).

-Kevin Ballard

···

On Thu, Dec 10, 2015, at 02:35 PM, Josh Avant via swift-evolution wrote:

Currently, when a reference-type adopts a protocol with a function
declared as `mutating`, the reference-type's implementation cannot
call that function internally. This is because the compiler enforces
an immutable `self` pointer value, and the `mutating` qualifier
implies that the function implementation may mutate that `self`
pointer value.

However, there seems to be a number of fairly reasonable situations
where a reference-type implementation of these `mutating` functions
may only want to mutate properties owned by `self`, but not the actual
`self` pointer value.

Consider this toy example:

``` import Foundation

protocol RandomDataTransformable { typealias TransformableType
var data: [TransformableType] { get set }

mutating func addRandomData() }

extension RandomDataTransformable where TransformableType == Int {
mutating func addRandomData() { let random =
Int(arc4random_uniform(6) + 1) data.append(random) } }

/////

// VALID struct NumberSource_Struct : RandomDataTransformable {
typealias TransformableType = Int var data: [Int] =

mutating func addData() { addRandomData() } }

// VALID class NumberSource_ClassDeclaration: NSObject,
RandomDataTransformable { typealias TransformableType = Int var
data: [Int] = }

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID class NumberSource_ClassImplementation: NSObject,
RandomDataTransformable { typealias TransformableType = Int var
data: [Int] =

func addData() { self.addRandomData() // Compiler Error: Cannot
use mutating member on immutable value: 'self' is immutable } } ```

Even despite the fact that the default implementation for
`addRandomData` does not mutate the `self` pointer value, reference-
type implementations are unable to call that function internally,
since it is marked as `mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by
objects which own instances of the reference-type (even though, again,
it may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations
to call the sample `addRandomData` implementation internally is to
qualify the whole `RandomDataTransformable` protocol as `class`. The
downside here is that this takes an otherwise perfectly reference- and
struct-compatible protocol + extension implementation and restricts it
to only apply to classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier
that applies when protocols are adopted by reference-types. The
qualifier would specify that the `self` pointer value itself may not
be mutated, but `self`'s properties may be, as appropriate.

Thoughts, feedback on this?

_________________________________________________
swift-evolution mailing list swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi Josh,

Currently, when a reference-type adopts a protocol with a function declared as `mutating`, the reference-type's implementation cannot call that function internally. This is because the compiler enforces an immutable `self` pointer value, and the `mutating` qualifier implies that the function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where a reference-type implementation of these `mutating` functions may only want to mutate properties owned by `self`, but not the actual `self` pointer value.

Consider this usage of the code you show below:

var s: RandomDataTransformable = NumberSource_Struct()
var ss = s

s.addRandomData()
// ss.data unchanged

var c: RandomDataTransformable = NumberSource_ClassDeclaration()
var cc = c

c.addRandomData()
// cc.data changed!

It seems it would be difficult to write fully generic code with a protocol that has this behavior, because you cannot rely on value semantics at all. To implement ‘mutating’ class methods correctly, you would in fact have to create a new class instance and re-assign to self every time, to preserve old references, since mutating the old value might break users of the protocol that are written as if the witness was a value type. This seems to defeat the whole purpose of using a reference type in fact.

It seems that if you really know what you’re doing, you can define a one-element struct that conforms to the protocol and contains the reference. This is in fact how Array, Dictionary and Set implement copy-on-write under the hood.

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

Now that I think about it, setters in protocols have the same issue with confusing behavior from code that expects value semantics. Hmm...

Slava

···

On Dec 10, 2015, at 2:35 PM, Josh Avant via swift-evolution <swift-evolution@swift.org> wrote:

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }
   
    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
   
    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
   
    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData` does not mutate the `self` pointer value, reference-type implementations are unable to call that function internally, since it is marked as `mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by objects which own instances of the reference-type (even though, again, it may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to call the sample `addRandomData` implementation internally is to qualify the whole `RandomDataTransformable` protocol as `class`. The downside here is that this takes an otherwise perfectly reference- and struct-compatible protocol + extension implementation and restricts it to only apply to classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that applies when protocols are adopted by reference-types. The qualifier would specify that the `self` pointer value itself may not be mutated, but `self`'s properties may be, as appropriate.

Thoughts, feedback on this?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hey Swift team can you tell the iTunes Connect team their website is down,
there is no way to get a hold of them.

···

On Thu, Dec 10, 2015 at 10:35 PM, Josh Avant via swift-evolution < swift-evolution@swift.org> wrote:

Currently, when a reference-type adopts a protocol with a function
declared as `mutating`, the reference-type's implementation cannot call
that function internally. This is because the compiler enforces an
immutable `self` pointer value, and the `mutating` qualifier implies that
the function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where
a reference-type implementation of these `mutating` functions may only want
to mutate properties owned by `self`, but not the actual `self` pointer
value.

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }

    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member
on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData`
does not mutate the `self` pointer value, reference-type implementations
are unable to call that function internally, since it is marked as
`mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by
objects which own instances of the reference-type (even though, again, it
may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to
call the sample `addRandomData` implementation internally is to qualify the
whole `RandomDataTransformable` protocol as `class`. The downside here is
that this takes an otherwise perfectly reference- and struct-compatible
protocol + extension implementation and restricts it to only apply to
classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that
applies when protocols are adopted by reference-types. The qualifier would
specify that the `self` pointer value itself may not be mutated, but
`self`'s properties may be, as appropriate.

Thoughts, feedback on this?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
 Wizard
james@supmenow.com
+44 7523 279 698

Thanks, Kevin!

Look forward to any other feedback!

···

On Thursday, December 10, 2015, Kevin Ballard via swift-evolution < swift-evolution@swift.org> wrote:

A couple of miscellaneous points:

* This problem only occurs when the class inherits a default
implementation of the method from a protocol extension. If the class
declares the method itself, then it's no longer a mutating method and
everything works fine.
* The problem exists because mutating functions are allowed to assign to
self, but methods on class types cannot assign to self, they can only
mutate the properties of self. This is why we cannot simply allow the call
to the inherited mutating method, as that method may reassign self.
* Classes can still call the method, they just have to say something like

var this = self
this.callMutatingMethod()

Yeah it's a little awkward, but it's not really all that bad for an edge
case like this.

Another potential workaround requires more work on the protocol side but
allows implementations to not care about the difference, which is to
provide a non-mutating variant in an extension restricted by Self:
AnyObject:

protocol P {
    var count: Int { get set }
    mutating func foo() -> String
}

extension P {
    mutating func foo() -> String {
        return _mutatingFoo(&self)
    }
}

extension P where Self: AnyObject {
    func foo() -> String {
        var this = self
        return _mutatingFoo(&this)
    }
}

private func _mutatingFoo<T: P>(inout value: T) -> String {
    value.count += 1
    return "foo"
}

Ultimately, I think there's some value in this proposal, but I worry about
adding a new keyword to handle an edge case like this when there's
workarounds available. Assuming we do add this feature, I'd suggest using
something like `mutating(ish)` instead of inventing a brand new keyword
(there's precedent in `private(set)`).

-Kevin Ballard

On Thu, Dec 10, 2015, at 02:35 PM, Josh Avant via swift-evolution wrote:

Currently, when a reference-type adopts a protocol with a function
declared as `mutating`, the reference-type's implementation cannot call
that function internally. This is because the compiler enforces an
immutable `self` pointer value, and the `mutating` qualifier implies that
the function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where
a reference-type implementation of these `mutating` functions may only want
to mutate properties owned by `self`, but not the actual `self` pointer
value.

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }

    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member
on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData`
does not mutate the `self` pointer value, reference-type implementations
are unable to call that function internally, since it is marked as
`mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by
objects which own instances of the reference-type (even though, again, it
may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to
call the sample `addRandomData` implementation internally is to qualify the
whole `RandomDataTransformable` protocol as `class`. The downside here is
that this takes an otherwise perfectly reference- and struct-compatible
protocol + extension implementation and restricts it to only apply to
classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that
applies when protocols are adopted by reference-types. The qualifier would
specify that the `self` pointer value itself may not be mutated, but
`self`'s properties may be, as appropriate.

Thoughts, feedback on this?

*_______________________________________________*
swift-evolution mailing list
swift-evolution@swift.org
<javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution

I'm sorry, but this isn't an area that the open source swift team can help with, please check with official apple channels on that.

-Chris

···

On Dec 11, 2015, at 11:42 AM, James Campbell via swift-evolution <swift-evolution@swift.org> wrote:

Hey Swift team can you tell the iTunes Connect team their website is down, there is no way to get a hold of them.

On Thu, Dec 10, 2015 at 10:35 PM, Josh Avant via swift-evolution <swift-evolution@swift.org> wrote:
Currently, when a reference-type adopts a protocol with a function declared as `mutating`, the reference-type's implementation cannot call that function internally. This is because the compiler enforces an immutable `self` pointer value, and the `mutating` qualifier implies that the function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where a reference-type implementation of these `mutating` functions may only want to mutate properties owned by `self`, but not the actual `self` pointer value.

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }
   
    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
   
    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
   
    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData` does not mutate the `self` pointer value, reference-type implementations are unable to call that function internally, since it is marked as `mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by objects which own instances of the reference-type (even though, again, it may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to call the sample `addRandomData` implementation internally is to qualify the whole `RandomDataTransformable` protocol as `class`. The downside here is that this takes an otherwise perfectly reference- and struct-compatible protocol + extension implementation and restricts it to only apply to classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that applies when protocols are adopted by reference-types. The qualifier would specify that the `self` pointer value itself may not be mutated, but `self`'s properties may be, as appropriate.

Thoughts, feedback on this?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
 Wizard
james@supmenow.com
+44 7523 279 698

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This would go too far.

It’s common to write a protocol with mutating methods just because the protocol designer expects that some adopting structs may want to mutate in their implementation. And in this case the goal of the protocol designer is certainly not to limit the protocol adoption to structs.

Here is an example:

  protocol DatabaseFetchable {
    mutating func awakeFromFetch()
  }
  extension DatabaseFetchable {
    func fetch() -> Self {
      var value = /* details omitted */
      value.awakeFromFetch()
      return value
    }
  }

The protocol does not care at all if awakeFromFetch mutates or not. But the protocol designer does: if the awakeFromFetch method were not declared mutating, many structs could simply not adopt it.

Gwendal Roué

···

Le 11 déc. 2015 à 20:34, Slava Pestov via swift-evolution <swift-evolution@swift.org> a écrit :

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

I guess the question is, does it even make sense to write a protocol that can be adopted by both a struct and a class, if the protocol has mutating members?

Slava

···

On Dec 11, 2015, at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 11 déc. 2015 à 20:34, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

This would go too far.

It’s common to write a protocol with mutating methods just because the protocol designer expects that some adopting structs may want to mutate in their implementation. And in this case the goal of the protocol designer is certainly not to limit the protocol adoption to structs.

Here is an example:

  protocol DatabaseFetchable {
    mutating func awakeFromFetch()
  }
  extension DatabaseFetchable {
    func fetch() -> Self {
      var value = /* details omitted */
      value.awakeFromFetch()
      return value
    }
  }

The protocol does not care at all if awakeFromFetch mutates or not. But the protocol designer does: if the awakeFromFetch method were not declared mutating, many structs could simply not adopt it.

Gwendal Roué

It seems it would be difficult to write fully generic code with a protocol
that has this behavior, because you cannot rely on value semantics at all.
To implement ‘mutating’ class methods correctly, you would in fact have to
create a new class instance and re-assign to self every time, to preserve
old references, since mutating the old value might break users of the
protocol that are written as if the witness was a value type. This seems to
defeat the whole purpose of using a reference type in fact.

Agreed. That description even seems to sound like a rehashing of the same
problem, but from a more strictly value-centric approach.

The 'one-element struct' solution isn't what I was looking for, here. I
wasn't trying to achieve copy-on-write of a whole object structure, but
instead writing more fully generic code. Also, not having to use an awkward
workaround (i.e. the var this = self; this.mutatingMethod()).

I guess the question is, does it even make sense to write a protocol that
can be adopted by both a struct and a class, if the protocol has mutating
members?

I think so. But I think part of the problem is that protocol members are
currently interpreted in terms of value semantics, but protocols are, of
course, widely used in reference semantics, too.

To me, it feels like we're lacking a degree of fidelity. That's why my
initial proposition was a `mutating`-ish operator that applies specifically
to this scenario, and provides a more nuanced bridging to reference
semantics (feels vaguely similar in nature to unowned vs. weak modifiers).

···

On Fri, Dec 11, 2015 at 11:44 AM, Slava Pestov <spestov@apple.com> wrote:

On Dec 11, 2015, at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 11 déc. 2015 à 20:34, Slava Pestov via swift-evolution < > swift-evolution@swift.org> a écrit :

For this reason I’m in favor of going in the opposite direction, and
prohibiting classes from conforming to protocols with mutating requirements.

This would go too far.

It’s common to write a protocol with mutating methods just because the
protocol designer expects that some adopting structs may want to mutate in
their implementation. And in this case the goal of the protocol designer is
certainly not to limit the protocol adoption to structs.

Here is an example:

protocol DatabaseFetchable {
  mutating func awakeFromFetch()
}
extension DatabaseFetchable {
  func fetch() -> Self {
    var value = /* details omitted */
    value.awakeFromFetch()
    return value
  }
}

The protocol does not care at all if awakeFromFetch mutates or not. But
the protocol designer does: if the awakeFromFetch method were not declared
mutating, many structs could simply not adopt it.

Gwendal Roué

I guess the question is, does it even make sense to write a protocol that
can be adopted by both a struct and a class, if the protocol has mutating
members?

Slava

I did report this problem as [SR-142] mutating function in protocol extension erroneously requires `var` declaration of class variables · Issue #42764 · apple/swift · GitHub a couple of days ago.

Greg Titus replied:

Looked into this briefly, and it's a lot harder than just changing the error checking, since m() has an implicit `inout P` first argument. Probably the solution is an automatic version of Gwendal's workaround: declaring a hidden `var p: P = c` and calling the method on p.

Gwendal Roué

···

Le 11 déc. 2015 à 18:22, Josh Avant via swift-evolution <swift-evolution@swift.org> a écrit :

Thanks, Kevin!

Look forward to any other feedback!

On Thursday, December 10, 2015, Kevin Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
A couple of miscellaneous points:

* This problem only occurs when the class inherits a default implementation of the method from a protocol extension. If the class declares the method itself, then it's no longer a mutating method and everything works fine.
* The problem exists because mutating functions are allowed to assign to self, but methods on class types cannot assign to self, they can only mutate the properties of self. This is why we cannot simply allow the call to the inherited mutating method, as that method may reassign self.
* Classes can still call the method, they just have to say something like

var this = self
this.callMutatingMethod()

Yeah it's a little awkward, but it's not really all that bad for an edge case like this.

Another potential workaround requires more work on the protocol side but allows implementations to not care about the difference, which is to provide a non-mutating variant in an extension restricted by Self: AnyObject:

protocol P {
    var count: Int { get set }
    mutating func foo() -> String
}

extension P {
    mutating func foo() -> String {
        return _mutatingFoo(&self)
    }
}

extension P where Self: AnyObject {
    func foo() -> String {
        var this = self
        return _mutatingFoo(&this)
    }
}

private func _mutatingFoo<T: P>(inout value: T) -> String {
    value.count += 1
    return "foo"
}

Ultimately, I think there's some value in this proposal, but I worry about adding a new keyword to handle an edge case like this when there's workarounds available. Assuming we do add this feature, I'd suggest using something like `mutating(ish)` instead of inventing a brand new keyword (there's precedent in `private(set)`).

-Kevin Ballard

On Thu, Dec 10, 2015, at 02:35 PM, Josh Avant via swift-evolution wrote:

Currently, when a reference-type adopts a protocol with a function declared as `mutating`, the reference-type's implementation cannot call that function internally. This is because the compiler enforces an immutable `self` pointer value, and the `mutating` qualifier implies that the function implementation may mutate that `self` pointer value.

However, there seems to be a number of fairly reasonable situations where a reference-type implementation of these `mutating` functions may only want to mutate properties owned by `self`, but not the actual `self` pointer value.

Consider this toy example:

import Foundation

protocol RandomDataTransformable {
    typealias TransformableType
    var data: [TransformableType] { get set }

    mutating func addRandomData()
}

extension RandomDataTransformable where TransformableType == Int {
    mutating func addRandomData() {
        let random = Int(arc4random_uniform(6) + 1)
        data.append(random)
    }
}

/////

// VALID
struct NumberSource_Struct : RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    mutating func addData() {
        addRandomData()
    }
}

// VALID
class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []
}

var numberSource = NumberSource_ClassDeclaration()
numberSource.addRandomData()

// INVALID
class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
    typealias TransformableType = Int
    var data: [Int] = []

    func addData() {
        self.addRandomData() // Compiler Error: Cannot use mutating member on immutable value: 'self' is immutable
    }
}

Even despite the fact that the default implementation for `addRandomData` does not mutate the `self` pointer value, reference-type implementations are unable to call that function internally, since it is marked as `mutating`.

Perhaps confusingly, `addRandomData` may be called by externally, by objects which own instances of the reference-type (even though, again, it may not called internally by the implementation, itself).

Currently, the only solution to allow reference-type implementations to call the sample `addRandomData` implementation internally is to qualify the whole `RandomDataTransformable` protocol as `class`. The downside here is that this takes an otherwise perfectly reference- and struct-compatible protocol + extension implementation and restricts it to only apply to classes, decreasing overall code reusability.

My proposal would be to introduce an intermediate mutation qualifier that applies when protocols are adopted by reference-types. The qualifier would specify that the `self` pointer value itself may not be mutated, but `self`'s properties may be, as appropriate.

Thoughts, feedback on this?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <javascript:_e(%7B%7D,'cvml','swift-evolution@swift.org');>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I just gave you an answer above: when the protocol designer does not care whether the protocol should be adopted by a struct or a class. Who knows? Maybe the user will adopt the protocol in an extension of an existing type, which happens to already be a struct or a class? Maybe the user will want to adopt the protocol in the base class of a class hierarchy? Who knows?

Gwendal Roué

···

Le 11 déc. 2015 à 20:44, Slava Pestov <spestov@apple.com> a écrit :

On Dec 11, 2015, at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:

Le 11 déc. 2015 à 20:34, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

This would go too far.

It’s common to write a protocol with mutating methods just because the protocol designer expects that some adopting structs may want to mutate in their implementation. And in this case the goal of the protocol designer is certainly not to limit the protocol adoption to structs.

Here is an example:

  protocol DatabaseFetchable {
    mutating func awakeFromFetch()
  }
  extension DatabaseFetchable {
    func fetch() -> Self {
      var value = /* details omitted */
      value.awakeFromFetch()
      return value
    }
  }

The protocol does not care at all if awakeFromFetch mutates or not. But the protocol designer does: if the awakeFromFetch method were not declared mutating, many structs could simply not adopt it.

Gwendal Roué

I guess the question is, does it even make sense to write a protocol that can be adopted by both a struct and a class, if the protocol has mutating members?

BTW, we have three workarounds to the initial problem so far, using the current state of Swift:

1. the one by Kevin Ballard below in the thread (the best one)
2. my first work around at [SR-142] mutating function in protocol extension erroneously requires `var` declaration of class variables · Issue #42764 · apple/swift · GitHub
3. declare a non-mutating protocol that inherits from the mutating one, and let classes adopt the non-mutating one, as in:

  protocol MutableP {
    mutating func f()
  }
  protocol P : MutableP {
    func f()
  }

The last workaround is a different in that it leaves room for structs that decide not to mutate in their implementation of the protocol.

You have an example of this approach at GitHub - groue/GRDB.swift: A toolkit for SQLite databases, with a focus on application development

Gwendal Roué

···

Le 11 déc. 2015 à 20:44, Slava Pestov <spestov@apple.com> a écrit :

On Dec 11, 2015, at 11:43 AM, Gwendal Roué <gwendal.roue@gmail.com <mailto:gwendal.roue@gmail.com>> wrote:

Le 11 déc. 2015 à 20:34, Slava Pestov via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

This would go too far.

It’s common to write a protocol with mutating methods just because the protocol designer expects that some adopting structs may want to mutate in their implementation. And in this case the goal of the protocol designer is certainly not to limit the protocol adoption to structs.

Here is an example:

  protocol DatabaseFetchable {
    mutating func awakeFromFetch()
  }
  extension DatabaseFetchable {
    func fetch() -> Self {
      var value = /* details omitted */
      value.awakeFromFetch()
      return value
    }
  }

The protocol does not care at all if awakeFromFetch mutates or not. But the protocol designer does: if the awakeFromFetch method were not declared mutating, many structs could simply not adopt it.

Gwendal Roué

I guess the question is, does it even make sense to write a protocol that can be adopted by both a struct and a class, if the protocol has mutating members?

Slava