`copy` operator doesn't clone a class instance

I found this when doing an experiment:

class C {
    var value: String = "abc"

    consuming func consumeAndDuplicate() -> (C, C) {
        let copy = copy self
        return (self, copy)
    }

    func dump() {
        print(value)
    }
}

func test() {
    let (x, y) = C().consumeAndDuplicate()
    x.value = "xyz"
    x.dump()
    y.dump()
}

// Output:
// xyz
// xyz

In my intuition if a value is consumed, all its resouces would be unavailable. But that's obviously not the case for class instances (IMO the memory allocated in heap is their resource). After seeing this behavior, I thought of two reasons why it's so:

  1. If copy operator really made a clone, it would be more likely a shallow copy, which wasn't that useful.

  2. Also, explicit copy and implicit copy have the same behavior. Since implicitly coping a class instance never clone it in the past, the current behavior is just following the convention.

But if so, what's the point to support consuming method for class? What's the difference does it bring?

You're right about point #2. Explicit copy and implicit copy are the same. There are two reasons why classes support consuming methods. 1) To help optimize ARC traffic. Since the compiler knows the method is going to release self, it doesn't need to add a retain release pair in the caller. 2) So that classes can conform to protocols with consuming method requirements.

1 Like

Reference types are always conceptually copyable, the copy operator just copies (actually retains) the pointer. The language does not forbid adding ownership modifiers to Copyable types, you just have to remember by doing so you are changing the calling convention.

Generally, the self parameter is passed with a "shared" convention, that is to say it's borrowing by default. For reference types, ownership affects ARC traffic, there could be some situations where changing self into consuming can have performance benefits.

--
For your example, I think its legit to mark the method as consuming, because self is part of the returned value. With consuming, there will be only 1 additional retain inside the body; while without consuming, there will be 2 additional retains.
So when the method is invoked on a independent value, for example C(). consumeAndDuplicate or (consume someC).consumeAndDuplicate(), you could have performance wins! [1]


  1. though actually, at a very lower level, swift_retain does not takes less time than swift_retain_n, but it surely does not worsen the case â†Šī¸Ž

2 Likes

Thanks. This is also @Nobody1707's point #1 (helping optimize ARC traffic). From this implementation perspective, both value type and reference type have the same behavior: when calling a consuming method on a class instance there is no retain call; when calling a consuming metohd on a value type's instance (with out-of-line storage) there is no retain call too.

However, if we look from notation perspective, IMO the behavior become inconsistent: a consuming method of a value type's instance gets an independent value to work on; but this isn't the case for class instance.

(Note: regarding the notation of "independent value", I read about it in earlier thread. Also it's mentioned in SE-0377, like "copy x is a borrowing operation on x that returns an independently owned copy of the current value of x.")

I think I'll stick to the implementation based approach to understand it.

It makes more sense if you think of the value of a class type as a pointer to its allocation instead of the contents of that allocation.

I thought of a better interpretation. The idea is to replace "independent value" with "copy". Below is how I understand it now:

  • consuming modifer is a hint to compiler that the function needs a copy of the value. If the caller doesn't access the value afterwards, the current value is passed to the function, which optimizes ARC traffic (if ARC is invovled); otherwise an implicit copy is made.
  • A copy operator creates a copy.

The exact definition of "copy" is out of the scope of SE-0377. I doubt if it's defined anywhere, but it can be summarized based on the behavior of implicit copy:

  • a copy of a struct or enum instance is an independent value (this is what value type is about)
  • a copy of a class instance is a copy of its reference