Compile error: partial application of 'mutating' method is not allowed: what is "partial application"?

struct Foo {
    let s: String

    func imNonMutating() { print(s) }
    mutating func imMutating() { }
}

var fooInstance = Foo(s: "Hi")

// can get a closure to non-mutating method
let bbb = fooInstance.imNonMutating
bbb()   // print out "Hi"
// so bbb is "bound" to fooInstance

// but cannot get a closure to mutating method
let aaa = fooInstance.imMutating    // compile error: partial application of 'mutating' method is not allowed
// is there anyway to get a "full" application of mutating method?
// I want to avoid extra level with this:
let ccc = { fooInstance.imMutating() }

Partial Application is when you supply some, but not all of the function arguments to the function for when it is later called.

No matter, when you write fooInstance.imMutating, it's not clear whether you mean you're requesting write-access to fooInstance for the entire lifetime of aaa or the duration of application of aaa (i.e. only when you do aaa()). It's not even clear if we should be allowed to defer write-access with the latter pattern. Swift is just being defensive and force you to be explicit (got a strong déjà vu feeling writing that, but I couldn't find the thread :thinking:).

1 Like

Also check the references in my response to Partial application of mutating methods.

1 Like

let ccc = { fooInstance.imMutating() }

ccc is at the same scope as aaa, so why no compile error here? isn't there same "lifetime" problem with ccc?

I want to do this:

SwiftUI.Button("Toggle", action: flag.toggle)

instead of:

Button("Toggle", action: { flag.toggle() })

in the hope of avoiding extra level of closure. All inside one View.

2 Likes

:thinking: Actually, you’re right, scratch that. There's nothing preventing aaa to be interpreted as ccc. Though there's still no way around it (other than to use ccc form).

In the case of ccc, the closure is capturing fooInstance, hence it can mutate the captured fooInstance even if the closure escapes out of the current scope (where fooInstance is undefined). On the other hand, if we write aaa assignment in its type form, we will see how it differs:

let aaa = Foo.imMutating(&fooInstance)

In the above case, it is a function call that happens to return a function result. The first function call has to finish its access to its parameter (fooInstance) and write it back as it returns. So, the resulting function (assigned to aaa) can't mutate fooInstance, because it is already out of scope and not captured when aaa is called later.

Two things:

  • I don't think we should be talking about "partial application" in diagnostics; we should say something like "use without an argument list".

  • There's really no reason we couldn't support this. In some cases, the function would have to be non-escaping, but that's okay. We would just need to initiate any accesses associated with the self argument within the closure rather than trying to capture the being-accessed self value.

Please file a bug.

6 Likes

SR-13976: Improve compiler error message: "partial application of ‘mutating’ method is not allowed"

SR-13977: Eliminate "partial application of ‘mutating’ method is not allowed"

3 Likes

Thanks!

let bbb = fooInstance.imNonMutating

Isn't this the same life scope problem as mutating method because the non-mutating method is bound to the instance and can access Foo instance properties? Why read is fine but write not?

No, in the case of non-mutating function, a copy of fooInstance is passed and it does not need to write back to the instance. that copy is kept inside imNonMutating. Lets make a small change to your original example to illustrate what is happening in each case:

struct Foo {
    var s: String

    func imNonMutating() { print(s) }
    mutating func imMutating( ) { s += "!"; print(s) }
}

var fooInstance = Foo(s: "Hi")

let bbb = fooInstance.imNonMutating
let ccc = { fooInstance.imMutating() }

bbb()   // print out "Hi"
fooInstance.s = "Hello"
bbb()  // still prints "Hi", not updated.

ccc() // This prints "Hello!"
print(fooInstance.s) // prints "Hello!", fooInstance is updated.
1 Like

So some simple looking "closure":

let bbb = fooInstance.imNonMutating

behind the scenes bbb is "carrying" a copy of the fooInstance? There must be some copy-on-write happening?

I have been quite surprise at how closure work in Swift: a global func is a closure and a struct/class method is also closure and either one can be passed to some func that expects a closure: they are obviously not the same. So is the compiler doing different thing depending on which kind of "closure" is being passed?

So I'm even more amazed at the power of SwiftUI's @​State: I can pass a non-mutating method as a closure to somewhere which mutates any @​State var inside the original View struct:

struct JustToggleMe: View {
    @State var flag = true

    // a non-mutating method that mutates a @State var
    // which can be passed around that can mutate flag
    func toggle() {
        flag.toggle()
    }

    var body: some View {
        VStack {
            Text("Flag is \(flag ? "true" : "False")")

            // this doesn't compile
//            Button("toggle", action: flag.toggle)   // Partial application of 'mutating' method is not allowed
            // but this does just fine
            Button("toggle", action: toggle)     // ??? what's going on here with "action: toggle"?
            // toggle must be carrying a copy of self? but still can mutate `flag`
            Button("{ flag.toggle() }", action: { flag.toggle() })
        }
    }
}

Swift closure seem very magical compare to Java or C++

You can also put it in closure to match the mutating behaviour:

var fooInstance = Foo(s: "Hi")
let ddd = { fooInstance.imNonMutating() }
let ccc = { fooInstance.imMutating() }

ccc() // Hi!

// ddd got updated
ddd() // Hi!

It calls into question of whether fooInstance.imNonMutating really should follow ccc structure since it's quite different from bbb, and is instead much closer to ddd. Fixing it isn't impossible (we can just assume ccc form), but I think this is more than a bug-fix (and could use SE).

The trick they use is ingenious, but is also quite simple. Since non-mutating requires that you don't mutate the "owner" of that function (usually self), you just store everything you want to mutate outside of it. If we do something like this:

class Storage { var value = fals }

struct Foo {
  var storage = Storage()

  /* non-mutating */ func toggle() {
    // Mutating `storage`, which is outside of `self`
    storage.value.toggle()
  }
}

let foo = Foo() // immutable
let bar = foo // make a copy
foo.storage.value // false
bar.storage.value // false
foo.toggle()
foo.storage.value // true
bar.storage.value // true
let baz = foo.toggle; baz()
foo.storage.value // false
bar.storage.value // false

The rest is to wrap Storage in a pretty property wrapper. Now we can take foo.toggle anywhere we want since it's non-mutating.

1 Like

I see how it works :+1:,

So bar is a "shallow" copy of foo. How to make a "deep" copy? Is there simple conventional way to do that, other than create a brand new Foo:

extension Foo {
    init(copy from: Foo) {
        self.storage = Storage(from.storage.value)
        ...
    }
}

I don't know of any other than the Obj-C's archaic NSCopy. TBH, I never need it ever since we switched over to value semantic + CoW for most types.

This is veering way off-topic, but since it is your topic, let me provide some brief pointers.

Generally, it is not a good practice to do eager deep copies of structs in Swift. Hiding an object or other reference to mutable state (e.g. a closure capturing reference to a variable) inside a struct should be a very deliberate and carefully considered design choice. You should be well aware of the purpose and consequenses of such choice and it is considered an advanced topic. There are three main reasons to do it:

  1. Creating a variable length and/or large data structure that preserves value semantic. This is where you use isKnownUniquelyReferenced to do copy-on-write. Doing it correctly is not very easy.

  2. Creating an internal cache of some sort that does not affect the actual value of an instance but helps performance by sharing the result of some common computations between values of that type. This will need synchronization if the values are used concurrently, which is not easy.

  3. Creating a hybrid Identifiable type when we design a data structure that has both aspects: You can freely copy it and change many properties of each copy independently, but some aspects of all copies remain the same. It is like a person appearing in different places with different outfits: You can change clothing, shoes, hair style, accessories and makeup in each setting but the person remains the same person. This is a very powerful and efficient abstraction being popularized by SwiftUI. In this design, you have a struct with a let-bound (fixed) object that can only be set when you create a new instance (not when assigning/copying it). This object provides its stable identity (like what a button does, or whether it is enabled). The rest of the properties are cosmetic or context dependent and can be cheaply changed without affecting other places like the position, size and style of each copy of that button.

As you see there is no case where we need an eager deep copy.

1 Like

This "compile error: partial application of 'mutating' method is not allowed" only occur for struct, no such error for class. All class instance methods are mutating?

class Foo {
    var n: Int

    init(_ n: Int) {
        self.n = n
    }
    // there is no mutating attribute for class instance methods, they are all mutating?
    /* mutating */ func imMutating() { n += 1; print(n) }
}

var fooInstance = Foo(0)

// for class, all methods are mutating?
let aaa = fooInstance.imMutating    // this is fine, no compile error

aaa()   // print 1
aaa()   // print 2

Yea, kinda. The mutation in reference types like class only includes replacing an entire instance with a new reference (they came from protocol methods really). All other methods are considered non-mutating. You can't force class members not to mutate anyhow using just var-let mechanism:

// Even if we don't allow mutation on c1
let c1 = SomeClass()
// we can still create a `var` to c1 which allow mutations
var c2 = c1
1 Like

For class let is meaningless, you can mutate it same like a var:

class Foo {
    var n: Int

    init(_ n: Int) {
        self.n = n
    }
    // there is no mutating attribute for class instance methods, they are all mutating?
    /* mutating */ func imMutating() { n += 1; print(n) }
}

// 👇👇👇👇
// doesn't matter `let` or `var`
let fooInstance = Foo(-1)

// even thought it's a `let`, still can mutate either `let` or `var`
fooInstance.n += 1          // works
fooInstance.imMutating()    // print 1

// for class, all methods are mutating?
// on `struct`, this cause compile error: partial application of ‘mutating’ method is not allowed
let aaa = fooInstance.imMutating    // this is fine, no compile error

aaa()   // print 2
aaa()   // print 3

print("\n")


// with class, this is possible:
let data = [Foo(1), Foo(2), Foo(3)]
data.forEach { element in   // <=== element is a `let`
    element.imMutating()
}

Why is let on class this way? Is it because ObjC legacy?

Class methods are nonmutating by default, so your imMutating is nonmutating.

If you want to have a mutating class method, you have to do it using a helper protocol:

protocol HelperProtocol {
    var n: Int { get }
    init(_ n: Int)
}
extension HelperProtocol {
    mutating func imMutating() {
        self = Self(n + 1)
    }
}
class Foo: HelperProtocol {
    var n: Int

    required init(_ n: Int) {
        self.n = n
    }
}

let fooInstance = Foo(-1)

fooInstance.n += 1          // works
fooInstance.imMutating()    // error: cannot use mutating member on immutable value: 'fooInstance' is a 'let' constant

let aaa = fooInstance.imMutating    // error: partial application of 'mutating' method is not allowed

aaa()   // print 2
aaa()   // print 3

print("\n")


let data = [Foo(1), Foo(2), Foo(3)]
data.forEach { element in   // <=== element is a `let`
    element.imMutating() // error: cannot use mutating member on immutable value: 'element' is a 'let' constant
}
1 Like
Terms of Service

Privacy Policy

Cookie Policy