스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift에서 noncopyable 유형 소비하기
Swift의 noncopyable 유형에 대해 자세히 알아보세요. Swift에서 ‘복사'가 어떤 개념인지 살펴보고, noncopyable 유형을 사용하기에 적합한 경우와 값 소유권으로 목적을 명시하는 방법에 대해 알아보세요.
챕터
- 0:00 - Introduction
- 0:30 - Agenda
- 0:50 - Copying
- 7:05 - Noncopyable Types
- 11:50 - Generics
- 19:12 - Extensions
- 21:24 - Wrap up
리소스
- Copyable
- Forum: Programming Languages
- Swift Evolution: Borrowing and consuming pattern matching for noncopyable types
- Swift Evolution: Noncopyable Generics
- Swift Evolution: Noncopyable Standard Library Primitives
관련 비디오
WWDC19
-
다운로드
안녕하세요 Swift 팀의 Kavon입니다! ’Swift에서 noncopyable 유형 소비하기’를 시작하겠습니다 Swift의 값은 고유한 값이 아닌데 그 이유는 복사가 가능하기 때문입니다 값이 고유하다는 보장이 있다는 것은 프로그래밍할 때 강력한 개념이 될 수 있습니다 따라서 기쁜 소식을 알려 드립니다 최근, Swift에 noncopyable 유형을 도입했습니다! 오늘 살펴볼 멋진 내용이 많이 있습니다 먼저, 복사가 작동하는 방식을 검토하겠습니다 그런 다음, 소유권과 noncopyable 유형에 대해 알아보고 마지막으로, 더 고급 주제를 다루겠습니다 예를 들면, noncopyable 유형을 일반적으로 사용하기와 해당 유형의 확장 프로그램 작성하기 등입니다 그럼 복사를 시작해 보죠! 저는 새로운 게임을 개발 중이기 때문에 Player 유형을 정의했고, 여기서 이모티콘은 아이콘을 나타냅니다 두 플레이어를 만들어 보겠습니다 지금까지 둘은 동일합니다 직관적으로, 한 플레이어 아이콘을 변경해도 다른 플레이어 아이콘에 영향을 미치지 않음을 압니다 이러한 직관의 이면을 단계별로 조사해 보겠습니다 첫 번째 줄에서 시작하면 player1은 개구리입니다 해당 변수의 콘텐츠는 Player를 구성하는 실제 데이터입니다 이는 값 유형인 struct이기 때문입니다 player2로 넘어가서 player1과 동일하게 만들겠습니다 하지만 이것은 무슨 의미일까요? player1의 복사본을 만든다는 의미입니다 변수를 복사하면 그 콘텐츠가 복사됩니다 따라서 player2의 아이콘을 변경하면 player1으로부터 독립적인 Player를 변경하는 것입니다 값을 복사하거나 파기하는 것을 생각할 필요도 없었습니다 Swift가 처리해 주니까요
하지만 Player가 참조 유형이었다면 어떨까요? 즉, struct에서 class로 변경했다는 의미입니다 이전과 동일한 코드를 사용하면 어떻게 될까요? 이 첫 번째 구문을 자세히 살펴보겠습니다
PlayerClass를 구성하면 객체가 별도로 할당되어 데이터를 저장합니다
player1의 콘텐츠는 해당 객체에 대해 자동으로 관리되는 참조가 됩니다 이것이 바로 참조 유형의 의미입니다 다음 구문에서 player2는 player1과 동일합니다 즉, 참조가 복사된다는 의미입니다 객체 자체가 아니라요! 이는 때때로 얕은 복사라고도 하며, 매우 빠르게 이루어집니다 두 플레이어 모두 동일한 객체를 참조하므로 아이콘을 변경하면 둘 모두에 대해 변경됩니다 따라서 이 어설션은 유효하지 않습니다 두 경우 모두에서 복사가 어떻게 동일하게 작동하는지 확인하세요 유일한 차이점은 값을 복사하는지 아니면 참조를 복사하는지입니다 참조 유형이 값 유형처럼 동작하게 만들 수 있습니다 깊은 복사를 수행하도록 이니셜라이저를 정의하면 됩니다 이 이니셜라이저는 하나의 예제입니다 객체와 객체가 가리키는 모든 것을 재귀적으로 다시 만듭니다
Icon의 이니셜라이저를 호출하여 이를 수행하죠 다른 Player의 아이콘을 사용해 다시 만듭니다 그러면 새로운 플레이어가 다른 플레이어와 참조를 공유하지 않죠
이전의 프로그램 상태로 돌아가 보겠습니다 player2를 player1과 동일하게 만든 후입니다 깊은 복사로 동작이 어떻게 달라지는지 확인해 보죠
현재, 두 플레이어는 모두 동일한 객체에 대한 참조입니다
플레이어의 필드에 쓰기 전에 그 자체에서 이니셜라이저를 호출해 깊은 복사본을 만듭니다
그러면 동일하지만 별도인 객체가 할당되며 다른 어떤 변수도 변이의 영향을 받지 않게 됩니다
실제로, 이것이 COW(Copy-On-Write)의 핵심이죠 변이 하에서 독립성이 부여되므로 값 유형과 동일한 동작이 구현됩니다
새로운 유형을 디자인할 때 그 값을 깊이 복사할 수 있는지 여부를 이미 제어할 수 있습니다 제어할 수 없는 것은 Swift가 자동 복사본을 만들 수 있는지 여부입니다 Copyable은 유형이 자동으로 복사될 수 있는 기능을 설명하는 새로운 프로토콜입니다 Sendable과 마찬가지로 멤버 요구 사항이 없습니다
Swift에서는 모든 것이 기본적으로 Copyable이라고 추론됩니다 모든 것이 그렇습니다 모든 유형은 자동으로 Copyable을 준수하려고 합니다 모든 일반 매개변수는 개발자가 입력하는 유형이 Copyable일 것을 자동으로 요구합니다 모든 프로토콜 및 연관된 유형은 구체적인 유형이 Copyable을 준수할 것을 자동으로 요구합니다
모든 박스형 프로토콜 유형은 Copyable로 자동 구성됩니다
제가 한 것처럼 Copyable을 직접 작성할 필요가 없습니다 눈에 보이지 않더라도 이미 존재합니다 Swift는 개발자가 복사 기능을 원한다고 가정합니다 Copyable 유형으로 작업하는 것이 훨씬 더 쉽기 때문입니다
하지만 복사하면 코드에 오류가 발생하기 쉬운 상황이 있습니다 백엔드를 위한 은행 이체를 모델링하는 유형을 가정해 보죠 실제로, 이체는 대기 중, 취소됨 또는 완료 상태입니다
해당 유형에 이체를 완료하는 run 메서드를 사용하겠습니다 이체를 예약해야 하므로 이를 수행하는 함수를 사용합니다 실수로 이체를 두 번 실행한다면 사용자가 분명 만족하지 않을 테니 다시 한 번 확인해 보죠 지연이 1초 미만이라면 즉시 실행하겠지만 return하는 것을 잊어버렸네요 따라서 누락되어 다시 실행합니다 이 간단한 실수로 인한 큰 비용 발생을 어떻게 막을 수 있을까요? 변수를 추가하여 이체의 상태를 추적할 수 있습니다 어설션이 이체를 다시 실행하려는 시도를 포착하도록 말이죠 하지만 해당 테스트 작성 없이는 어설션이 버그를 발견할 수 없죠 따라서 이런 버그가 남아 있어 백엔드 서비스가 중단될 수 있겠죠 실제로, 이 schedule 함수에는 또 다른 버그가 있습니다! sleep 작업이 취소되면 어떻게 될지 생각해 보세요 호출자가 발생한 오류를 주의 깊게 확인하지 않으면 이체를 취소하는 것을 잊어버릴 수 있습니다
취소할 것을 기억하는 deinit을 BankTransfer에 도입할 수 있죠 하지만 이것은 실제로 매우 쓸모가 없습니다
startPayment 함수를 자세히 살펴보세요 이체가 예약되었는지 추적하기 위해 이체의 복사본을 유지합니다 이것은 문제입니다 이체의 모든 복사본이 파기되어야 이체의 deinit이 실행되니까요 이것이 문제의 근원입니다 프로그램에 이 이체의 복사본이 몇 개나 존재하는지를 제어할 수 없습니다 값을 복사하는 기능이 유형의 올바른 기본값인 경우가 많지만 일부 상황에서는 유형이 noncopyable인 것이 더 낫습니다 BankTransfer 문제는 잠시 제쳐 두고 noncopyable 유형에 대해 알아보겠습니다
FloppyDisk를 모델링하고 싶다고 가정해 보죠 이렇게 작성된 유형은 기본적으로 Copyable을 준수합니다 하지만 준수를 선언하는 지점에서 Copyable이라는 단어 앞에 물결 기호(~)를 사용해 Copyable에 대한 기본 준수를 억제합니다 이제 FloppyDisk에는 Copyable 준수가 전혀 없습니다! ~Copyable을 개발자가 표시한 유형에 대해 Copyable의 부재를 선언하는 것으로 생각하세요 해당 플로피를 복사하려고 하면 어떻게 될까요? 복사는 지원되지 않으므로 그 대신에 Swift가 이를 소비하죠
consume을 작성하여 이를 명시적으로 지정할 수 있지만 이는 이와 관계없이 발생합니다
변수 소비 시 변수를 초기화되지 않은 상태로 두고 값을 취합니다
따라서 해당 소비 전에 시스템 디스크만 초기화됩니다
소비는 시스템 디스크의 콘텐츠를 외부의 백업 디스크로 이동시킵니다 이후 시스템 디스크를 읽으면 아무것도 없어 오류가 발생합니다
이제 새로운 디스크를 생성하는 이 함수를 고려합니다 이 함수가 format을 호출하면 변수 결과가 어떻게 될까요? 알기 어렵습니다 함수의 서명에서 해당 디스크에 대해 어떤 소유권이 필요한지 선언하지 않기 때문입니다 copyable 매개변수의 경우 이에 대해 생각할 필요가 없었죠 format이 디스크의 복사본을 효과적으로 수신하니까요 하지만 noncopyable 매개변수의 경우에는 함수가 해당 값에 대해 어떤 소유권을 갖는지 선언해야 합니다 복사할 수 없기 때문입니다
첫 번째 소유권 종류는 소비라고 합니다 함수가 호출자로부터 인수를 가져갈 것임을 의미합니다 이것은 개발자 소유이므로 변이시킬 수도 있습니다
하지만 여기서 소비를 사용하면 문제가 될 수 있습니다 format이 디스크를 돌려주지 않고 아무것도 반환하지 않기 때문이죠 하지만 생각해 보면 디스크 포맷에는 임시 접근 권한만 필요합니다
임시 접근 권한이 있다는 것은 해당 항목을 차용하는 것입니다 차용하면 인수에 대한 읽기 권한이 부여되죠, let 바인딩처럼요
실제로 거의 모든 매개변수와 메서드는 Copyable 유형에 이렇게 작동하죠
차이점은 명시적으로 차용된 인수는 소비나 변이가 불가능하고 복사만 할 수 있다는 것입니다 format 함수는 결국 디스크를 변이시켜야 하므로 이를 위해 차용을 사용할 수도 없습니다
이미 익숙한 마지막 소유권 종류는 inout입니다! 또는 메서드에서 이와 동등한 것은 작성된 변이입니다
inout은 호출자의 변수에 대한 임시 쓰기 접근 권한을 제공합니다 개발자는 쓰기 접근 권한이 있어 매개변수를 소비할 수 있습니다 하지만 함수가 종료되기 전 어느 시점에 inout 매개변수를 다시 초기화해야 합니다 return할 때 호출자는 값을 기대하기 때문입니다 BankTransfer 예제를 다시 살펴보겠습니다 이제 이를 소비 가능한 리소스로 모델링할 수 있으니까요 먼저, BankTransfer를 복사할 수 없는 struct로 지정하고 그 run 메서드를 consuming으로 표시하여 자신을 위해 호출자로부터 값을 가져오도록 했습니다 이 두 가지 변경 사항만으로 더 이상 어설션이 필요 없습니다 Swift는 같은 이체에 run 메서드를 두 번 호출할 수 없음을 보장하죠 실제로, 소유권은 매우 정밀하게 추적되므로 소유권이 ’실행’되는 대신에 파기되면 작업을 트리거하는 deinit을 struct에 추가할 수 있습니다 ’run’ 메서드의 종료도 자동으로 자신을 파기하므로 discard self를 작성하겠습니다 그러면 deinit을 호출하지 않고 파기합니다 schedule 함수의 버그들은 어떻게 되는지 살펴보겠습니다 먼저, transfer 매개변수에 대한 소유권을 추가해야 합니다 ’schedule’은 마지막 사용이므로 소비로 지정하는 것이 좋습니다 이제 컴파일해 보면 Swift에서 버그가 0이 된 것을 볼 수 있죠 if 구문은 누락되므로 이체를 두 번 소비할 수 있습니다 return을 추가하면 이를 방지합니다 이제 나머지 다른 버그는 어떨까요? 이 버그도 정의를 통해 해결되었습니다 Schedule은 해당 이체의 마지막 소유자이므로 Sleep가 발생하면 deinit이 실행되며 이로 인해 이체가 취소됩니다
noncopyable 유형은 프로그램의 정확성을 높이는 데 유용합니다 일반 코드를 비롯해 모든 곳에서 사용할 수 있습니다 Swift 6에서는 이제 가능합니다 noncopyable 제네릭을 사용해서요! 이는 새로운 제네릭 시스템이 아닙니다 Swift의 기존 제네릭 모델에 기반합니다 제네릭에 대해 알고 있는 내용을 한번 상기시켜 보죠
우선, Swift의 다양한 유형으로 이루어진 우주를 고려해 보세요 모든 유형이 행복하게 공존해 왔습니다 String이든, Command라는 저만의 유형이든 관계없이요
제 프로토콜인 Runnable은 이 우주의 하위 공간을 정의하며 이를 준수하는 유형을 포함합니다 현재, 준수하는 것이 없으므로 비어 있습니다 하지만 Runnable 준수로 Command를 확장하면 해당 점이 Runnable 공간으로 이동합니다 제네릭 이면의 핵심적인 아이디어는 준수 제약 조건이 일반 유형을 설명한다는 것입니다 execute라는 이 일반 함수를 사용해 이에 대해 생각해 보죠 꺾쇠괄호 안에 있는 T를 확인하세요 새로운 일반 유형 매개변수를 선언하여 이 우주의 어떤 유형을 나타내지만 어떤 것인지는 모릅니다 제가 Copyable은 어디에나 있다고 말했던 것을 기억하시나요? 이 T에 대한 기본 제약 조건이 있습니다 Copyable을 준수하기 위해 입력 유형이 필요합니다
Command는 기본적으로 Copyable을 준수하며 Runnable도 Copyable에서 가져옵니다 실제로, 최근까지 Swift의 유형 우주 전체가 위장된 상태의 Copyable이었습니다
즉, 이 공간의 모든 유형이 execute 함수에 전달될 수 있죠 유일한 제약 조건이 T가 Copyable을 준수한다는 것이니까요
따라서 Command가 Runnable도 준수하더라도 이는 여전히 이 더 넓은 공간인 Copyable에 존재합니다 여기서 특정 유형은 Runnable일 수 있지만, 그렇지 않을 수도 있죠 일반 매개변수 T는 추가 준수가 있는 유형을 제외하지 않습니다 작성된 대로, execute 함수는 Copyable 이외의 다른 준수 사항을 필요로 하지 않으면서 실행될 수 있습니다
하지만 execute 함수 구현을 위해 사실 T가 Runnable이기를 원합니다 run 메서드를 호출해야 하기 때문입니다 따라서 where 절을 사용해 T에 Runnable 제약 조건을 추가합니다
이때 T에 대해 허용되는 유형의 공간을 추가로 제약했습니다 이제 더 좁은 공간인 Runnable 및 Copyable입니다 여기에는 Command가 포함되지만 이제 String은 제외됩니다 String에는 Runnable 준수가 부재하기 때문입니다
Swift 5.9부터는 우주가 확장되었습니다 Copyable 준수 사항이 없는 유형이 있기 때문입니다 예를 들어 빛나는 새 BankTransfer 유형은 준수하지 않으므로 그 점이 외부에 있습니다 ~Copyable을 작성해야만 Copyable 준수를 억제할 수 있으므로 저는 이 더 넓은 공간을 이렇게 부르겠습니다
개발자에게 익숙한 Swift의 유형 대부분은 Copyable을 준수합니다 이들은 ~Copyable 내에 어떻게 포함될까요?
이전과 마찬가지로 이 더 넓은 공간 내에서는 특정 유형이 Copyable을 준수한다고 가정할 수 없습니다 Copyable일 수 있지만 그렇지 않을 수도 있습니다 이러한 방식으로 ~Copyable을 읽어야 합니다
Any 유형은 어떨까요? 항상 Copyable이었으며 그래야 합니다 거의 모든 프로그래밍 언어에서 any 유형은 Copyable입니다
이제 noncopyable 제네릭에 대해 살펴볼 수 있습니다 앞에서 살펴봤던 Runnable 프로토콜로 시작해 보죠
유형 우주는 현재 이런 모습입니다 모든 Runnable 값은 Copyable입니다
BankTransfer는 Copyable이 아니니 Runnable일 수도 없습니다 하지만 BankTransfer가 준수하기를 원하니 일반적으로 사용할 수 있죠 Runnable 유형을 복사하는 기능은 프로토콜의 기본 사항이 아니므로 ~Copyable을 추가해 Copyable 제약 조건을 Runnable에서 제거합니다
이렇게 하면 계층 구조가 변경되므로 Copyable 공간이 Runnable을 포함하지 않고, 이와 중복됩니다 Command는 Runnable 및 Copyable로 해당 중복 내에 놓이죠 다음으로, Runnable 준수로 BankTransfer를 확장하면 그 점이 Copyable이 아닌 상태로 Runnable 내로 이동합니다
일반 함수인 execute를 다시 살펴보겠습니다
일반 매개변수 T에 대해 여전히 기본 제약 조건이 있습니다 따라서 Runnable이면서 동시에 Copyable인 Command 등의 유형만 execute에서 허용됩니다
~Copyable을 사용해 Copyable 제약 조건을 T에서 제거해 보죠
제약 조건을 제거하면 허용 유형이 모든 Runnable 유형으로 확장되죠 execute 함수는 T가 Copyable이 아닐 수 있음을 나타내고 있습니다 이것이 핵심적인 내용입니다 정규 제약 조건은 더 구체적으로 지정하는 방법으로 허용되는 유형의 범위를 좁힙니다 반면에 물결 기호 제약 조건은 덜 구체적으로 지정해 범위를 넓히죠 이제 이러한 모든 이론을 실제로 사용해 보겠습니다 저는 Runnable 유형을 Job이라는 새로운 struct 내부에 래핑하려고 합니다 저는 일반 매개변수 Action을 사용해 Job을 정의했습니다 Action은 Runnable이며 Copyable이 아닐 수 있습니다 하지만 지금까지 제가 작성한 내용으로는 오류가 발생합니다 struct Job의 기본값은 Copyable을 준수하는 것이므로 Copyable 데이터만 포함할 수 있습니다 noncopyable을 다른 noncopyable 안에 저장하는 두 방법이 있습니다 class 복사는 참조만 복사하므로 class 내부에 있어야 하거나 포함하는 유형 자체에 대해 Copyable을 억제해야 합니다 저는 두 번째 방법을 사용해 Job을 noncopyable로 지정하겠습니다
여전히 Action에 대해 Command 유형을 사용할 수 있습니다 Action이 Copyable 유형의 표시를 막지 않기 때문입니다 Job이 Action을 복사할 필요가 없음을 약속하므로 noncopyable 유형도 작동합니다 하지만 Action에 입력하는 유형이 Copyable임을 안다면 어떨까요? 작업의 컨테이너일 뿐이므로 Job이 복사될 수 있습니다
Job이 조건부로 Copyable이라고 선언해 이를 허용할 수 있죠 이 확장 프로그램은 Action이 Copyable이면 Job이 Copyable이죠
이것이 우리 우주에서는 어떻게 보일까요?
Job이 Copyable인지 여부는 모릅니다 Action에 대해 구체적인 유형을 입력할 때까지는요 이제 Command를 입력해 보겠습니다
Command가 Copyable이므로 Command-Job도 Copyable입니다
하지만 Action을 BankTransfer로 지정하면 조건부 준수가 충족되지 않으므로 BankTransfer-Job은 Copyable이 아닙니다
따라서 noncopyable 제네릭 이면의 전체 아이디어는 기본 Copyable 제약 조건을 제거하는 것입니다 noncopyable 일반 매개변수로 유형을 정의하는 방법을 살펴봤죠 해당 유형의 확장 프로그램을 더 자세히 살펴보겠습니다 Action에 대해 getter 메서드를 정의하려고 한다고 가정해 보죠
Job의 일반적인 확장 프로그램을 사용해 추가하겠습니다
이를 호출하면 잘 작동하지만 Action의 복사본을 제공하지 않나요? 그렇습니다 작업을 반환하면 작업이 복사되죠 이는 확장 프로그램의 오류가 아닙니다
이 일반 확장 프로그램은 기본적으로 Action이 Copyable인 Job으로 제한되기 때문입니다
이 getter는 올바릅니다 BankTransfer 같은 작업에 대해 호출할 수 없기 때문입니다 모든 확장 프로그램은 이렇게 작동합니다 확장된 유형 범위의 모든 일반 매개변수는 Copyable로 제한되며 여기에는 프로토콜의 Self가 포함됩니다
확장 프로그램이 이렇게 작동하게 하면 멋진 이점이 있습니다 Job이 실제로 제가 작성하지 않은 JobKit 모듈의 일부라 가정해 보죠 Cancellable 유형을 설명하기 위한 프로토콜이 있습니다 noncopyable 유형이 무엇인지 모르지만 어쨌든 Job이 준수하기를 원한다고 가정해 보겠습니다 이 확장 프로그램을 작성할 수 있고 잘 작동할 테니 괜찮습니다
준수는 기본적으로 Action이 Copyable이라는 조건으로 지정되기 때문입니다 일반적으로 Action은 그렇지 않을 수 있으니까요 또한 Action이 Copyable이면 Job도 같아서 Cancellable을 준수하죠
따라서 이 Job 유형을 게시할 수 있으며 Copyable 유형으로만 작업하는 프로그래머가 이를 사용할 수 있죠 이제 이 확장 프로그램이 Copyable 여부에 관계없이 모든 작업에 적용되기를 원한다면 어떨까요?
이 확장 프로그램에서 Action의 Copyable 제약 조건을 해제합니다 Action을 Copyable로 가정 않고 Job이 Cancellable을 준수합니다
Swift에서 복사의 작동 방식과 문제 발생 가능 부분을 살펴봤죠 Noncopyable 유형은 소유권에 대해 생각할 필요 없이 프로그램 정확성을 높일 수 있는 유용한 도구입니다 Optional, UnsafePointer Result가 포함된 표준 라이브러리에 noncopyable 제네릭을 도입하기 위한 첫걸음을 떼었습니다 noncopyable 제네릭 패턴 일치의 차용 및 소비 noncopyable 표준 라이브러리 프리미티브에 관한 Swift Evolution 제안 사항을 읽어 보면 더 자세히 알아볼 수 있습니다 Swift Programming Language 책에서 자세히 알아볼 수도 있죠 더 일반적으로, COW와 일반 유형 디자인 모범 사례에 대해 알아보려면 WWDC 2019의 Modern Swift API Design을 참고하세요 감사합니다 멋진 WWDC를 즐기세요!
-
-
0:52 - Player as a struct
struct Player { var icon: String } func test() { let player1 = Player(icon: "🐸") var player2 = player1 player2.icon = "🚚" assert(player1.icon == "🐸") }
-
1:55 - Player as a class
class PlayerClass { var icon: String init(_ icon: String) { self.icon = icon } } func test() { let player1 = PlayerClass("🐸") let player2 = player1 player2.icon = "🚚" assert(player1.icon == "🐸") }
-
3:00 - Deeply copying a PlayerClass
class PlayerClass { var data: Icon init(_ icon: String) { self.data = Icon(icon) } init(from other: PlayerClass) { self.data = Icon(from: other.data) } } func test() { let player1 = PlayerClass("🐸") var player2 = player1 player2 = PlayerClass(from: player2) player2.data.icon = "🚚" assert(player1.data.icon == "🐸") } struct Icon { var icon: String init(_ icon: String) { self.icon = icon } init(from other: Icon) { self.icon = other.icon } }
-
5:10 - Copyable BankTransfer
class BankTransfer { var complete = false func run() { assert(!complete) // .. do it .. complete = true } deinit { if !complete { cancel() } } func cancel() { /* ... */ } } func schedule(_ transfer: BankTransfer, _ delay: Duration) async throws { if delay < .seconds(1) { transfer.run() } try await Task.sleep(for: delay) transfer.run() } func startPayment() async { let payment = BankTransfer() log.append(payment) try? await schedule(payment, .seconds(3)) } let log = Log() final class Log: Sendable { func append(_ transfer: BankTransfer) { /* ... */ } }
-
7:46 - Copying FloppyDisk
struct FloppyDisk: ~Copyable {} func copyFloppy() { let system = FloppyDisk() let backup = consume system load(system) // ... } func load(_ disk: borrowing FloppyDisk) {}
-
8:18 - Missing ownership for FloppyDisk
struct FloppyDisk: ~Copyable { } func newDisk() -> FloppyDisk { let result = FloppyDisk() format(result) return result } func format(_ disk: FloppyDisk) { // ... }
-
9:00 - Consuming ownership
struct FloppyDisk: ~Copyable { } func newDisk() -> FloppyDisk { let result = FloppyDisk() format(result) return result } func format(_ disk: consuming FloppyDisk) { // ... }
-
9:26 - Borrowing ownership
struct FloppyDisk: ~Copyable { } func newDisk() -> FloppyDisk { let result = FloppyDisk() format(result) return result } func format(_ disk: borrowing FloppyDisk) { var tempDisk = disk // ... }
-
9:55 - Inout ownership
struct FloppyDisk: ~Copyable { } func newDisk() -> FloppyDisk { var result = FloppyDisk() format(&result) return result } func format(_ disk: inout FloppyDisk) { var tempDisk = disk // ... disk = tempDisk }
-
10:28 - Noncopyable BankTransfer
struct BankTransfer: ~Copyable { consuming func run() { // .. do it .. discard self } deinit { cancel() } consuming func cancel() { // .. do the cancellation .. discard self } }
-
11:10 - Schedule function for noncopyable BankTransfer
func schedule(_ transfer: consuming BankTransfer, _ delay: Duration) async throws { if delay < .seconds(1) { transfer.run() return } try await Task.sleep(for: delay) transfer.run() }
-
12:12 - Overview of conformance constraints
struct Command { } protocol Runnable { consuming func run() } extension Command: Runnable { func run() { /* ... */ } } func execute1<T>(_ t: T) {} func execute2<T>(_ t: T) where T: Runnable { t.run() } func test(_ cmd: Command, _ str: String) { execute1(cmd) execute1(str) execute2(cmd) execute2(str) // expected error: 'execute2' requires that 'String' conform to 'Runnable' }
-
15:50 - Noncopyable generics: 'execute' function
protocol Runnable: ~Copyable { consuming func run() } struct Command: Runnable { func run() { /* ... */ } } struct BankTransfer: ~Copyable, Runnable { consuming func run() { /* ... */ } } func execute2<T>(_ t: T) where T: Runnable { t.run() } func execute3<T>(_ t: consuming T) where T: Runnable, T: ~Copyable { t.run() } func test() { execute2(Command()) execute2(BankTransfer()) // expected error: 'execute2' requires that 'BankTransfer' conform to 'Copyable' execute3(Command()) execute3(BankTransfer()) }
-
18:05 - Conditionally Copyable
struct Job<Action: Runnable & ~Copyable>: ~Copyable { var action: Action? } func runEndlessly(_ job: consuming Job<Command>) { while true { let current = copy job current.action?.run() } } extension Job: Copyable where Action: Copyable {} protocol Runnable: ~Copyable { consuming func run() } struct Command: Runnable { func run() { /* ... */ } }
-
19:27 - Extensions of types with noncopyable generic parameters
extension Job { func getAction() -> Action? { return action } } func inspectCmd(_ cmdJob: Job<Command>) { let _ = cmdJob.getAction() let _ = cmdJob.getAction() } func inspectXfer(_ transferJob: borrowing Job<BankTransfer>) { let _ = transferJob.getAction() // expected error: method 'getAction' requires that 'BankTransfer' conform to 'Copyable' } struct Job<Action: Runnable & ~Copyable>: ~Copyable { var action: Action? } extension Job: Copyable where Action: Copyable {} protocol Runnable: ~Copyable { consuming func run() } struct Command: Runnable { func run() { /* ... */ } } struct BankTransfer: ~Copyable, Runnable { consuming func run() { /* ... */ } }
-
20:14 - Cancellable for Jobs with Copyable actions
protocol Cancellable { mutating func cancel() } extension Job: Cancellable { mutating func cancel() { action = nil } }
-
21:00 - Cancellable for all Jobs
protocol Cancellable: ~Copyable { mutating func cancel() } extension Job: Cancellable where Action: ~Copyable { mutating func cancel() { action = nil } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.