study record

[Swift] Noncopyable structs and enums λ³Έλ¬Έ

Swift

[Swift] Noncopyable structs and enums

asong 2025. 3. 3. 01:06

πŸ’« SE-0390Noncopyable structs and enums

 

Motivation

ν˜„μž¬ Swiftμ—μ„œ μ‘΄μž¬ν•˜λŠ” λͺ¨λ“  νƒ€μž…μ€ λ³΅μ‚¬ κ°€λŠ₯(copyable) ν•˜λ―€λ‘œ, ν•΄λ‹Ή νƒ€μž…μ˜ 값에 λŒ€ν•΄ μ—¬λŸ¬ 개의 λ™μΌν•˜κ³  κ΅ν™˜ κ°€λŠ₯ν•œ ν‘œν˜„μ„ 생성할 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ, 볡사 κ°€λŠ₯ν•œ ꡬ쑰체(struct)와 μ—΄κ±°ν˜•(enum)은 κ³ μœ ν•œ μžμ›(unique resource)을 λͺ¨λΈλ§ν•˜λŠ” 데 μ ν•©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

반면, 클래슀(class)λŠ” 객체가 ν•œ 번 μ΄ˆκΈ°ν™”λ˜λ©΄ κ³ μœ ν•œ 정체성(unique identity)을 κ°€μ§€λ―€λ‘œ κ³ μœ ν•œ μžμ›μ„ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ ν΄λž˜μŠ€μ˜ μ°Έμ‘°(reference)λŠ” μ—¬μ „νžˆ 볡사 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ—, ν΄λž˜μŠ€λŠ” 항상 μžμ›μ˜ 곡유 μ†Œμœ κΆŒ(shared ownership)을 μš”κ΅¬ν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ 곡유 μ†Œμœ κΆŒμ€ λ‹€μŒκ³Ό 같은 뢀가적인 μ˜€λ²„ν—€λ“œλ₯Ό μ΄ˆλž˜ν•©λ‹ˆλ‹€.

  • 객체의 전체 수λͺ…이 λΆˆλΆ„λͺ…ν•˜κΈ° λ•Œλ¬Έμ— νž™ ν• λ‹Ή(heap allocation)이 ν•„μš”ν•¨
  • ν˜„μž¬ 객체λ₯Ό κ³΅μœ ν•˜λŠ” μ†Œμœ μžμ˜ 수λ₯Ό μΆ”μ ν•˜κΈ° μœ„ν•΄ μ°Έμ‘° μΉ΄μš΄νŒ…(reference counting)이 ν•„μš”ν•¨

λ˜ν•œ, 곡유 μ ‘κ·Ό 방식은 객체의 APIλ₯Ό λ³΅μž‘ν•˜κ²Œ λ§Œλ“€κ±°λ‚˜, μ•ˆμ „μ„± 문제λ₯Ό μ΄ˆλž˜ν•˜κ±°λ‚˜, 좔가적인 μ˜€λ²„ν—€λ“œλ₯Ό λ°œμƒμ‹œν‚€λŠ” κ²½μš°κ°€ λ§ŽμŠ΅λ‹ˆλ‹€.

ν˜„μž¬ Swiftμ—λŠ” κ³ μœ ν•œ μ†Œμœ κΆŒ(unique ownership)을 κ°€μ§€λŠ” κ³ μœ ν•œ μžμ›μ„ ν‘œν˜„ν•˜λŠ” νƒ€μž…μ„ μ •μ˜ν•˜λŠ” λ©”μ»€λ‹ˆμ¦˜μ΄ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

Proposed Solution

μš°λ¦¬λŠ” ꡬ쑰체(struct)와 μ—΄κ±°ν˜•(enum) νƒ€μž…μ΄ noncopyable κ°€λŠ₯ν•˜λ„λ‘ μ„ μ–Έν•  수 μžˆλ„λ‘ μ œμ•ˆν•˜λ©°, 이λ₯Ό μœ„ν•΄ μƒˆλ‘œμš΄ 문법 ~Copyable을 μ‚¬μš©ν•˜μ—¬ μ•”μ‹œμ μΈ μ œλ„€λ¦­ μ œμ•½μ„ μ–΅μ œ(suppress)ν•˜λŠ” 방식을 λ„μž…ν•˜λ € ν•©λ‹ˆλ‹€.

~Copyable을 μ‚¬μš©ν•˜λ©΄ Swiftκ°€ μ œλ„€λ¦­ νƒ€μž…μ΄ μžλ™μœΌλ‘œ Copyable을 λ”°λ₯΄λ„둝 ν•˜λŠ” μ•”μ‹œμ μΈ μ œμ•½μ„ μ œκ±°ν•  수 μžˆλ‹€.

noncopyable νƒ€μž…μ˜ 값은 항상 κ³ μœ ν•œ μ†Œμœ κΆŒ(unique ownership)을 κ°€μ§€λ©°, μ ˆλŒ€λ‘œ 볡사될 수 μ—†μŠ΅λ‹ˆλ‹€(적어도 Swift의 μ•”μ‹œμ  볡사 λ©”μ»€λ‹ˆμ¦˜μ„ ν†΅ν•΄μ„œλŠ” λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€).

noncopyable ꡬ쑰체와 μ—΄κ±°ν˜•μ˜ 값은 κ³ μœ ν•œ 정체성(unique identity)을 κ°€μ§€λ―€λ‘œ, 클래슀처럼 deinit μ„ μ–Έμ„ κ°€μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” ν•΄λ‹Ή κ³ μœ  μΈμŠ€ν„΄μŠ€μ˜ 생λͺ… μ£ΌκΈ°κ°€ 끝날 λ•Œ μžλ™μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€.

struct FileDescriptor: **~Copyable** {
  private var fd: Int32

  init(fd: Int32) { self.fd = fd }

  func write(buffer: Data) {
    buffer.withUnsafeBytes { 
      write(fd, $0.baseAddress!, $0.count)
    }
  }

  **deinit** {
    close(fd)
  }
}

NonCopyable νƒ€μž…μœΌλ‘œ μ„ μ–Έλ˜μ—ˆκΈ° λ•Œλ¬Έμ—,

  • 값을 볡사할 수 μ—†μŒ (즉, 두 개 μ΄μƒμ˜ λ™μΌν•œ FileDescriptor μΈμŠ€ν„΄μŠ€λ₯Ό λ§Œλ“€ 수 μ—†μŒ)
  • 값은 항상 κ³ μœ ν•œ μ†Œμœ κΆŒ(unique ownership)을 가짐
  • 값이 λ³΅μ‚¬λ˜μ§€ μ•ŠμœΌλ―€λ‘œ κ° μΈμŠ€ν„΄μŠ€κ°€ μœ μΌν•œ 파일 핸듀을 관리
  • λΆˆν•„μš”ν•œ μ°Έμ‘° μΉ΄μš΄νŒ…(reference counting)κ³Ό 곡유 λΉ„μš©μ΄ μ—†μŒ β‡’ λΉ λ₯΄κ³  μ•ˆμ „ν•œ μ„±λŠ₯

이제 Swiftμ—μ„œ ~Copyable을 ν™œμš©ν•˜λ©΄ ꡬ쑰체(struct)λ₯Ό 톡해 κ³ μœ ν•œ λ¦¬μ†ŒμŠ€λ₯Ό μ•ˆμ „ν•˜κ³  효율적으둜 관리할 수 μžˆμŠ΅λ‹ˆλ‹€.

  • κΈ°μ‘΄μ—λŠ” 클래슀λ₯Ό μ‚¬μš©ν•΄μ•Ό ν–ˆμ§€λ§Œ, μ΄μ œλŠ” Heap ν• λ‹Ή 없이, 더 μ•ˆμ „ν•˜κ³  λΉ λ₯΄κ²Œ 관리 κ°€λŠ₯
  • deinit μ§€μ›μ„ 톡해 μžλ™ λ¦¬μ†ŒμŠ€ ν•΄μ œ κ°€λŠ₯
  • λΆˆν•„μš”ν•œ 볡사 λ°©μ§€λ‘œ μ„±λŠ₯ μ΅œμ ν™”

λ”°λΌμ„œ, ~Copyable은 파일 λ””μŠ€ν¬λ¦½ν„°, λ„€νŠΈμ›Œν¬ μ†ŒμΌ“, GPU 버퍼 같은 κ³ μœ ν•œ μžμ›(unique resource)을 κ΄€λ¦¬ν•˜λŠ” 데 맀우 μ ν•©ν•œ κΈ°λŠ₯μž…λ‹ˆλ‹€.

 

Noncopyable νƒ€μž…μ„ ν¬ν•¨ν•˜λŠ” 경우

  • ꡬ쑰체(struct)κ°€ λΉ„볡사 νƒ€μž…μ„ μ €μž₯ ν”„λ‘œνΌν‹°(stored property)둜 ν¬ν•¨ν•˜κ±°λ‚˜,
  • μ—΄κ±°ν˜•(enum)이 λΉ„볡사 νƒ€μž…μ„ μ—°κ΄€ κ°’(associated value)으둜 ν¬ν•¨ν•˜λŠ” 경우,

β†’ ν•΄λ‹Ή ꡬ쑰체 λ˜λŠ” μ—΄κ±°ν˜•λ„ λ°˜λ“œμ‹œ ~Copyable을 μ„ μ–Έν•΄μ•Ό ν•©λ‹ˆλ‹€.

즉, noncopyable νƒ€μž…μ„ ν¬ν•¨ν•˜λŠ” νƒ€μž… μ—­μ‹œ μžλ™μœΌλ‘œ noncopyable νƒ€μž…μ΄ λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

struct SocketPair: ~Copyable {
  var in, out: FileDescriptor
}

enum FileOrMemory: ~Copyable {
  // write to an OS file
  case file(FileDescriptor)
  // write to an array in memory
  case memory([UInt8])
}

// **ERROR**: **copyable value type cannot contain noncopyable members**
struct FileWithPath {
  var file: FileDescriptor
  var path: String
}

 

Class와 noncopyable

 

Class의 κ²½μš°μ—λŠ” ~Copyable을 μ±„νƒν•˜μ§€ μ•Šμ•„λ„ noncopyable ν”„λ‘œνΌν‹°λ₯Ό κ°€μ§ˆ 수 μžˆλ‹€.

class SharedFile {
  var file: FileDescriptor
}

클래슀(class) νƒ€μž… 선언은 ~Copyable을 μ‚¬μš©ν•  수 μ—†μŒ

λͺ¨λ“  클래슀 νƒ€μž…μ€ μ—¬μ „νžˆ λ³΅μ‚¬ κ°€λŠ₯(copyable) ν•˜λ©°, ~Copyable을 μ„ μ–Έν•  수 μ—†μŠ΅λ‹ˆλ‹€.

μ΄λŠ” ν΄λž˜μŠ€κ°€ κ°μ²΄μ— λŒ€ν•œ μ°Έμ‘°(reference)λ₯Ό 볡사할 λ•Œ, μ°Έμ‘° μΉ΄μš΄νŒ…(retain/release)을 톡해 λ©”λͺ¨λ¦¬λ₯Ό κ΄€λ¦¬ν•˜λŠ” 방식 λ•Œλ¬Έμž…λ‹ˆλ‹€. 즉, 클래슀 μΈμŠ€ν„΄μŠ€ μžμ²΄κ°€ λ³΅μ‚¬λ˜λŠ” 것이 μ•„λ‹ˆλΌ κ°μ²΄μ˜ 참쑰만 λ³΅μ‚¬λ˜λ―€λ‘œ, ~Copyable을 μ μš©ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.

βœ… μ¦‰, ν΄λž˜μŠ€λŠ” 항상 μ°Έμ‘° νƒ€μž…(reference type)μ΄λ―€λ‘œ 볡사 κ°€λŠ₯ν•˜λ©°, ~Copyable을 μ μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

// **ERROR: classes must be `Copyable`**
class SharedFile: ~Copyable {
  var file: FileDescriptor
}

 

Noncopyable νƒ€μž…μ΄ μ œλ„€λ¦­ νƒ€μž… 인자둜 μ‚¬μš©λ  수 μ—†μŒ

 

ν˜„μž¬ Swift의 μ œλ„€λ¦­ μ‹œμŠ€ν…œμ€ μ œλ„€λ¦­ νƒ€μž…μ΄ 항상 Copyableν•  것을 μš”κ΅¬ν•©λ‹ˆλ‹€.

즉, noncopyable νƒ€μž…(~Copyable)을 μ œλ„€λ¦­ 인자둜 μ „λ‹¬ν•˜λŠ” 것은 λΆˆκ°€λŠ₯ν•©λ‹ˆλ‹€.

이둜 인해 noncopyable νƒ€μž…(~Copyable)이 ν•  수 μ—†λŠ” 것듀:

  1. ν”„λ‘œν† μ½œ μ€€μˆ˜ λΆˆκ°€ (단, Sendable은 μ˜ˆμ™Έ)βœ… FileDescriptorλŠ” 비볡사 νƒ€μž…μ΄λ―€λ‘œ CustomStringConvertible κ°™μ€ ν”„λ‘œν† μ½œμ„ λ”°λ₯Ό 수 μ—†μŒ.
  2. βœ… 단, Sendable ν”„λ‘œν† μ½œμ€ μ˜ˆμ™Έμ μœΌλ‘œ κ΅¬ν˜„ κ°€λŠ₯.
  3. struct FileDescriptor: ~Copyable, CustomStringConvertible { ... } // ❌ λΆˆκ°€λŠ₯!
  4. μ œλ„€λ¦­ ν•¨μˆ˜μ˜ νƒ€μž… 인자둜 μ‚¬μš© λΆˆκ°€
func process<T>(_ value: T) { ... }

let fd = FileDescriptor(fd: 10)
process(fd) // ❌ λΆˆκ°€λŠ₯!
  1. Any λ˜λŠ” λ‹€λ₯Έ 쑴재 νƒ€μž…(existential)으둜 μΊμŠ€νŒ… λΆˆκ°€
let anyValue: Any = FileDescriptor(fd: 10) // ❌ λΆˆκ°€λŠ₯!

비볡사 νƒ€μž…μ€ Anyλ‚˜ λ‹€λ₯Έ 쑴재 νƒ€μž…μœΌλ‘œ λ³€ν™˜ν•  수 μ—†μŒ.

  • βœ… λΉ„볡사 νƒ€μž…(~Copyable)은 자체적으둜만 μ‚¬μš© κ°€λŠ₯ν•˜λ©°,
  • ❌ μ œλ„€λ¦­ 인자둜 μ „λ‹¬ν•˜κ±°λ‚˜, ν”„λ‘œν† μ½œμ„ λ”°λ₯΄κ±°λ‚˜, Any둜 λ³€ν™˜ν•˜λŠ” λ“± 볡사가 κ°€λŠ₯ν•œ μ»¨ν…μŠ€νŠΈμ—μ„œλŠ” μ‚¬μš© λΆˆκ°€λŠ₯

β†’

즉, ~Copyableν•œ ꡬ쑰체 λ˜λŠ” μ—΄κ±°ν˜•μ€ 였직 자기 μžμ‹ λ§Œ ν•˜μœ„ νƒ€μž…(subtype)으둜 κ°€μ§ˆ 수 있음

λ‹€λ₯Έ νƒ€μž…κ³Όμ˜ λ³€ν™˜μ΄ λΆˆκ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ— λ³΅μ‚¬λ₯Ό ν—ˆμš©ν•˜λŠ” μ»¨ν…μŠ€νŠΈμ—μ„œ μ‚¬μš©λ  수 μ—†μŒ

 

noncopyable struct μΈμŠ€ν„΄μŠ€λ₯Ό ν• λ‹Ήν•˜λŠ” 것 μžμ²΄κ°€ consume

let x = FileDescriptor()
let y = x
use(x) // ERROR: x consumed by assignment to `y`

 

noncopyable types을 νŒŒλΌλ―Έν„°λ‘œ μ‚¬μš© μ‹œ,  borrowing, consuming, or inout convention μ‚¬μš© ν•„μˆ˜

// Redirect a file descriptor
// Require exclusive access to the FileDescriptor to replace it
func redirect(_ file: inout FileDescriptor, to otherFile: borrowing FileDescriptor) {
  dup2(otherFile.fd, file.fd)
}

// **Error: Noncopyable parameter must specify its ownership**
func redirect(_ file: FileDescriptor, to otherFile: borrowing FileDescriptor) {
  dup2(otherFile.fd, file.fd)
}

// Write to a file descriptor
// Only needs shared access
func write(_ data: [UInt8], to file: borrowing FileDescriptor) {
  data.withUnsafeBytes {
    write(file.fd, $0.baseAddress, $0.count)
  }
}

// Close a file descriptor
// Consumes the file descriptor
func close(file: consuming FileDescriptor) {
  close(file.fd)
}

 

 

ν•¨μˆ˜λ„ λ§ˆμ°¬κ°€μ§€

extension FileDescriptor {
  mutating func replace(with otherFile: borrowing FileDescriptor) {
    dup2(otherFile.fd, self.fd)
  }

  // borrowing by default
  func write(_ data: [UInt8]) {
    data.withUnsafeBytes {
      write(file.fd, $0.baseAddress, $0.count)
    }
  }

  consuming func close() {
    close(fd)
  }
}

 

Noncopyable νƒ€μž…μ˜ ν”„λ‘œνΌν‹° μ„ μ–Έ

 

1. Noncopyable νƒ€μž…μ„ μ €μž₯ ν”„λ‘œνΌν‹°λ‘œ μ„ μ–Έ

클래슀 λ˜λŠ” ~Copyable structλŠ” Noncopyable νƒ€μž…μ„ μ €μž₯ ν”„λ‘œνΌν‹°(let λ˜λŠ” var)둜 μ„ μ–Έν•  수 μžˆλ‹€.

let μ €μž₯ ν”„λ‘œνΌν‹°

  • let ν”„λ‘œνΌν‹°λŠ” 였직 borrow만 κ°€λŠ₯
  • 즉, 읽을 μˆ˜λŠ” μžˆμ§€λ§Œ λ³€κ²½ν•˜κ±°λ‚˜ consumeν•  수 μ—†μŒ

var μ €μž₯ ν”„λ‘œνΌν‹°

  • var ν”„λ‘œνΌν‹°λŠ” borrow와 mutateλͺ¨λ‘ κ°€λŠ₯
  • ν•˜μ§€λ§Œ 일반적으둜 consume은 λΆˆκ°€λŠ₯ (μ œν•œμ μœΌλ‘œ κ°€λŠ₯)
    • 이유: ν”„λ‘œνΌν‹°κ°€ μ†ŒλΉ„λ˜λ©΄ ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€(ꡬ쑰체/클래슀)κ°€ 무효 μƒνƒœκ°€ 될 수 있기 λ•Œλ¬Έ
struct NonCopyableResource: ~Copyable {
    var id: Int
}

struct Container: ~Copyable {
    let resource: NonCopyableResource // βœ… let ν”„λ‘œνΌν‹° (borrow만 κ°€λŠ₯)

    func consumeResource() {
        let resourceCopy = move(resource) // ❌ ERROR: 'resource' cannot be consumed because it is immutable
    }

    func move(_ resource: consuming NonCopyableResource) {
        consume resource
    }
}
struct Container: ~Copyable {
    var resource: NonCopyableResource // βœ… var ν”„λ‘œνΌν‹° (borrow + mutate κ°€λŠ₯)

		mutating func updateResource(id: Int) {
        resource.id = id // βœ… mutate κ°€λŠ₯ (μˆ˜μ • κ°€λŠ₯)
    }

    func consumeResource() {
        let resourceCopy = move(resource) // ❌ ERROR: 'resource' cannot be consumed
    }

    func move(_ resource: consuming NonCopyableResource) {
        consume resource
    }
}

 

2. Noncopyable νƒ€μž…μ„ computed ν”„λ‘œνΌν‹°λ‘œ μ„ μ–Έ

  • λͺ¨λ“  νƒ€μž…μ€ Noncopyable νƒ€μž…μ„ 계산 ν”„λ‘œνΌν‹°λ‘œ μ„ μ–Έ κ°€λŠ₯
  • get μ ‘κ·ΌμžλŠ” μ†Œμœ κΆŒ(owned value)을 λ°˜ν™˜ν•  수 있음 β†’ 즉, ν˜ΈμΆœν•œ μͺ½μ—μ„œ 값을 consumeν•  수 있음
  • set μ ‘κ·ΌμžλŠ” newValueλ₯Ό consumeν•˜λŠ” λ°©μ‹μœΌλ‘œ λ°›μŒ β†’ 즉, κΈ°μ‘΄ 값을 consumeν•˜μ—¬ μƒˆλ‘œμš΄ κ°’μœΌλ‘œ ꡐ체 κ°€λŠ₯

consuming get을 ν™œμš©ν•œ μ†Œμœ κΆŒ 이전

  • consuming get을 μ‚¬μš©ν•˜λ©΄ 일뢀 값을 μ†Œμœ κΆŒκ³Ό ν•¨κ»˜ λ°˜ν™˜ν•  수 있음
  • μ΄λŠ” 래퍼(wrapper) νƒ€μž…μ—μ„œ λ‚΄λΆ€ κ°’μ˜ μ†Œμœ κΆŒμ„ λ„˜κ²¨μ£ΌλŠ” λ°©μ‹μœΌλ‘œ 유용
struct FileDescriptorWrapper: ~Copyable {
  private var _value: FileDescriptor

  var value: FileDescriptor { // 'self' is borrowed and cannot be consumed ?!
    consuming get { return _value }
  }
}

 

Classμ—μ„œ Noncopyable μ €μž₯ ν”„λ‘œνΌν‹° μ‚¬μš©

 

1) 클래슀의 μ €μž₯ ν”„λ‘œνΌν‹°μ™€ 동적 배타성 검사(Dynamic Exclusivity Checking)

Swiftμ—μ„œλŠ” 객체(Class Instance)κ°€ μ—¬λŸ¬ μ°Έμ‘°(reference)λ₯Ό κ°€μ§ˆ 수 있기 λ•Œλ¬Έμ—,

λ™μ‹œμ— 같은 μ €μž₯ ν”„λ‘œνΌν‹°λ₯Ό μˆ˜μ •ν•˜λŠ” 것을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ "동적 배타성 검사(Dynamic Exclusivity Checking)"λ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.

이 동적 검사(runtime check)λŠ” ~Copyable μ €μž₯ ν”„λ‘œνΌν‹°μ—λ„ μ μš©λ©λ‹ˆλ‹€.

즉, λ™μ‹œμ— borrow(λŒ€μ—¬)와 mutate(λ³€κ²½)κ°€ μ‹œλ„λ˜λ©΄, λŸ°νƒ€μž„ 였λ₯˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

SwiftλŠ” borrow μƒνƒœμ—μ„œ mutateκ°€ λΆˆκ°€λŠ₯ν•˜λ„λ‘ κ°•μ œν•¨

class Foo {
  var fd: FileDescriptor

  init(fd: FileDescriptor) { self.fd = fd }
}

func update(_: inout FileDescriptor, butBorrow _: borrow FileDescriptor) {}

func updateFoo(_ a: Foo, butBorrowFoo b: Foo) {
  update(&a.fd, butBorrow: b.fd)
}

let foo = Foo(fd: FileDescriptor())

// Will trap at runtime when foo.fd is borrowed and mutated at the same time
updateFoo(foo, butBorrowFoo: foo)

 

Noncopyable λ³€μˆ˜κ°€ escaping ν΄λ‘œμ €μ—μ„œ 캑처될 λ•Œμ˜ λ™μž‘

 

nonescaping vs. escaping ν΄λ‘œμ €μ—μ„œ Noncopyable λ³€μˆ˜ 캑처 차이

  • nonescaping ν΄λ‘œμ €λŠ” 유효 λ²”μœ„(scope)κ°€ ν•œμ •λ¨ β†’ λ”°λΌμ„œ λ³€μˆ˜λ₯Ό borrow(λŒ€μ—¬)ν•  수 있음
  • escaping ν΄λ‘œμ €λŠ” ν”„λ‘œκ·Έλž¨μ΄ μ‹€ν–‰λ˜λŠ” λ™μ•ˆ μ–Έμ œλ“ μ§€ 호좜될 수 있음 β†’ λ”°λΌμ„œ λ³€μˆ˜μ˜ μ†Œμœ κΆŒμ„ λͺ…ν™•ν•˜κ²Œ 관리해야 함

πŸ’‘ μ¦‰, escaping ν΄λ‘œμ €μ—μ„œ Noncopyable λ³€μˆ˜λ₯Ό μΊ‘μ²˜ν•˜λ©΄, λ³€μˆ˜λŠ” 항상 borrow μƒνƒœκ°€ 되며 consume(μ†ŒλΉ„)ν•  수 μ—†μŒ

  • escaping ν΄λ‘œμ €λŠ” ν˜„μž¬ μŠ€μ½”ν”„λ₯Ό λ²—μ–΄λ‚˜μ„œλ„ 싀행될 수 있음
  • 일반적으둜 Swift의 Copyableν•œ 값듀은 escaping ν΄λ‘œμ €μ—μ„œ μ•ˆμ „ν•˜κ²Œ 볡사 κ°€λŠ₯
  • ν•˜μ§€λ§Œ λΉ„볡사(~Copyable) λ³€μˆ˜λŠ” 볡사할 수 μ—†μœΌλ―€λ‘œ, consumeν•˜λ©΄ 원본이 사라짐

🚨 μ¦‰, consumeν•˜λ©΄ λ³€μˆ˜κ°€ 더 이상 μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ, 이후 싀행될 ν΄λ‘œμ €μ—μ„œ ν•΄λ‹Ή λ³€μˆ˜λ₯Ό μ‚¬μš©ν•  수 μ—†μŒ

import Dispatch

struct FileDescriptor: ~Copyable {
  private var fd: Int32

  init(fd: Int32) { self.fd = fd }

  func read() {
    print("Reading from fd: \\(fd)")
  }

  consuming func close() {
    print("Closing fd: \\(fd)")
  }
}

func escape(_ closure: @escaping () -> Void) {
  DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
    closure()
  }
}

func test() {
  let file = FileDescriptor(fd: 42)

  escape {
    file.read() // βœ… `borrow` κ°€λŠ₯ (읽기 μ „μš©)
    // file.close() // ❌ ERROR: `file`은 `escaping` ν΄λ‘œμ €μ—μ„œ `borrow` μƒνƒœμ΄λ―€λ‘œ `consume` λΆˆκ°€
  }
}

test()

 

ꡬ쑰체(struct) 및 μ—΄κ±°ν˜•(enum)의 deinit νŠΉμ§•

  • 클래슀의 deinitκ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ, struct λ° enum의 deinitμ—μ„œλ„ 였λ₯˜λ₯Ό 던질 수 μ—†μŒ
  • deinit λ‚΄λΆ€μ—μ„œ selfλŠ” borrow μƒνƒœ
    • 즉, mutate(λ³€κ²½)ν•˜κ±°λ‚˜ consume(μ†ŒλΉ„)ν•  수 μ—†μŒ
struct FileDescriptor: ~Copyable {
  private var fd: Int32

  init(fd: Int32) { self.fd = fd }

  deinit {
    print("Closing file descriptor \\(fd)")
    // self.fd = -1 ❌ ERROR: `deinit` λ‚΄λΆ€μ—μ„œλŠ” `mutate` λΆˆκ°€
    // consume self ❌ ERROR: `deinit` λ‚΄λΆ€μ—μ„œλŠ” `consume` λΆˆκ°€
  }
}

 

 

λ°”κΉ₯ ~Copyable이 λ¨Όμ € deinit 됨.

struct Inner: ~Copyable {
  deinit { print("destroying inner") }
}

struct Outer: ~Copyable {
  var inner = Inner()
  deinit { print("destroying outer") }
}

do {
  _ = Outer()
}

// destroying outer
// destroying inner

 

discard selfλ₯Ό ν™œμš©ν•˜μ—¬ deinit μ‹€ν–‰ μ–΅μ œ

Noncopyable νƒ€μž…μ—μ„œ deinit이 μ‹€ν–‰λ˜μ§€ μ•Šλ„λ‘ ν•˜κΈ° μœ„ν•΄ discard self μ—°μ‚°μžλ₯Ό λ„μž…

  • discard selfλŠ” ν•΄λ‹Ή 객체(self)의 수λͺ…을 μ’…λ£Œν•˜μ§€λ§Œ, deinit을 ν˜ΈμΆœν•˜μ§€ μ•ŠμŒ
  • 이λ₯Ό 톡해 λ¦¬μ†ŒμŠ€λ₯Ό 직접 μ œμ–΄ κ°€λŠ₯
struct FileDescriptor: ~Copyable {
  private var fd: Int32

  deinit {
    close(fd)
  }

  consuming func close() {
    close(fd)

    // The lifetime of `self` ends here, triggering `deinit` (and another call to `close`)!
  }
}
struct FileDescriptor: ~Copyable {
  private var fd: Int32

  // 파일 λ””μŠ€ν¬λ¦½ν„°λ₯Ό 닫을 λ•Œ, `deinit`이 μ‹€ν–‰λ˜μ§€ μ•Šλ„λ‘ μ–΅μ œ
  consuming func close() throws {
    if close(fd) != 0 {
      throw CloseError(errno)
    }

    discard self // βœ… `deinit`이 μ‹€ν–‰λ˜μ§€ μ•ŠμŒ
  }
}

 

 

μ‘°κ±΄λΆ€λ‘œ deinit을 μ‹€ν–‰ν• μ§€ μ—¬λΆ€λ₯Ό κ²°μ • κ°€λŠ₯

βœ…discard selfλ₯Ό μ‚¬μš©ν•˜λ©΄ deinit이 μ‹€ν–‰λ˜μ§€ μ•Šκ³ , consume selfλ₯Ό μ‚¬μš©ν•˜λ©΄ deinit μ‹€ν–‰λ¨

struct MemoryBuffer: ~Copyable {
  private var address: UnsafeRawPointer

  init(size: Int) throws {
    guard let address = malloc(size) else {
      throw MallocError()
    }
    self.address = address
  }

  deinit {
    free(address)
  }

  consuming func takeOwnership(if condition: Bool) -> UnsafeRawPointer? {
    if condition {
      // Save the memory buffer and give it to the caller, who
      // is promising to free it when they're done.
      let address = self.address
      **discard self**
      return address
    } else {
      // We still want to free the memory if we aren't giving it away.
      **_ = consume self**
      return nil
    }
  }
}

'Swift' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

[Swift] consume, consuming, borrowing  (0) 2025.03.03