๊ด€๋ฆฌ ๋ฉ”๋‰ด

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