본문 바로가기
WWDC/2024

Consume noncopyable types in Swift

by 찜토끼 2024. 8. 8.

https://developer.apple.com/videos/play/wwdc2024/10170/

 

Consume noncopyable types in Swift - WWDC24 - Videos - Apple Developer

Get started with noncopyable types in Swift. Discover what copying means in Swift, when you might want to use a noncopyable type, and how...

developer.apple.com

개요

Noncopyable type 에 대해 알아보세요.


Copying

  • 기존 개념
    • Value type: Deep copying
    • Reference type: Shallow copying
  •  The Copyable Protocol (new)
    • 모든 타입, 제네릭, 프로토콜이 기본적으로 Copyable을 따른다. 굳이 명시할 필요도 없음

 

Noncopyable Types

struct FloppyDisk: ~Copyable { }

Copyable 하면 안 되는 케이스를 위함.

~Copyable은 사실 Copyable을 포함하는 개념이다. Copyable 할 수도 있고 아닐 수도 있으니 Copyable 하게 취급하지 말라는 것. 아래에 자세히 서술.

consume instead of copy

struct FloppyDisk: ~Copyable {}

func copyFloppy() {
  let system = FloppyDisk()
  let backup = consume system
  load(system) // compile error!
  // ...
}

위 예제에서 `consume` 이 일어난 후 실제 content 는 system 변수에서 backup 변수로 옮겨진다.

`consume` 후에 system 변수에 접근할 시 에러가 발생한다. 이미 system 변수는 아무것도 아님.

Ownership

struct FloppyDisk: ~Copyable { }

func newDisk() -> FloppyDisk {
  let result = FloppyDisk()
  format(result)
  return result
}

func format(_ disk: consuming FloppyDisk) {
  // caller 로부터 consume. disk is now belong to this function. 이제 내꺼다.
  // newDisk 함수에서의 return 절에서는 이미 consume 된 변수에 접근하므로 에러가 발생
}

func load(_ disk: borrowing FloppyDisk) {
  // temporary access. 말 그대로 빌려온다.
  // read-only. let binding 을 생각하면 된다
  
  var tempDisk = disk // disk는 borrowed 된 것이기 때문에 consume 될 수 없음. 오류발생.
}

func formatV2(_ disk: inout FloppyDisk) {
  // 이미 아는 그것. temporary write-access
}

 

Copyable 변수라면 함수 파라미터로 넘기는 순간 copy가 일어나겠지만, Noncopyable 타입의 변수는 디폴트 동작이 copy가 아니기 때문에 정확히 어떤 ownership 인지를 명시해주어야 한다.

Ownership의 종류: consuming, borrowing, inout.

 

Generics & Extensions 과 함께 알아보자

Any 를 포함해서 기존의 모든 타입은 Copyable 이다. 그러나 이제는 Noncopyable 타입도 존재하게 되었다. 모든 타입을 만족시키는 프로토콜+제네릭을 위해서는 이것을 염두에 두어야 함.

`~Copyable` constrant: Copyable일 수도 있고 아닐 수도 있다.

func execute<T>(_ t: T) { // T: Copyable 이 생략된 것. T는 무조건 Copyable이다.
  ...
}

func extcute<T>(_ t: consuming T)
  where T: ~Copyable { // T는 Copyable일 수도 있고 아닐 수도 있다 (might not be copyable)
  ...
}

일반적인 constraint는 좀 더 세분화한 영역으로 범위를 제한한다. 그러나 ~(tilde) constraint 는 오히려 범위를 넓힌다고 할 수 있다. 아래 다이어그램을 참고.

예시로 알아보기

Error: Stored property 'action' has noncopyable type 'Action?'

`struct Job`은 사실 `struct Job: Copyable` 이 생략된 것. Copyable struct는 값 복사가 필요하기 때문에 오직 Copyable한 데이터만 담을 수 있다. 따라서 Noncopyable 할지도 모르는 Action 을 프로퍼티로 가질 수 없다. 이것을 어떻게 해결할 수 있을까?
- 방법1) Job을 class 로 변경한다. class 는 오직 reference 만 copy하기 때문에 괜찮다.
- 방법2) Job도 ~Copyable 로 만든다.

extension 의 선언을 통해 Job은 Action 타입이 Copyable 한지 아닌지에 따라 Copyable 여부가 결정된다. 아래 다이어그램을 참고.