Files
coollang/samples/05_move_errors.cool
coolguyandClaude Opus 5 2e67b74376 move: move/affinity 검사 — v0 fast path 완성
보안 정리 (ii) — safe code에서 capability는 복제·위조되지 않는다 — 를 코드로
닫는다. 검사는 전부 함수 로컬 데이터플로우이고 전역 분석이 없다.

affinity의 뿌리는 capability다. 필드로 가진 타입은 전이적으로 affine이며
고정점까지 돌려 상호 재귀 타입도 유도한다. 이 전이가 없으면 wrapper 하나를
복사해 capability가 사실상 복제되므로 정리가 깨진다. copyable 선언과 affine
필드의 공존은 오류다.

구현한 규칙:
- affine 값은 소유 자리로 갈 때 move된다(own 파라미터, 반환, struct 저장,
  컨테이너 삽입, let 바인딩, by-move capture). moved 이후 사용은 오류이고
  진단이 어디서 소비됐는지를 말한다
- 분기 병합은 보수적 합집합. 한 분기에서라도 moved면 병합 이후 moved
- 빌린 값은 탈출하지 못한다: 반환, struct 저장, 소유 자리로 넘기기 전부 거부
- use의 전염: 빌린 값을 capture한 클로저는 그 자체가 빌린 값이라 소유 자리로
  갈 수 없다. 별도의 nonescaping 개념 없이 use 규칙 하나로 닫힌다
- callable affinity: affine 값을 capture한 클로저는 affine fn이며 fn 자리에
  갈 수 없다

자율 결정 둘:
- 클로저는 mut 바인딩을 capture할 수 없다. spawn만 막는 특수 규칙 대신
  일반 규칙으로 뒀다 — v0에 참조가 없으므로 별칭도 조용한 복사도 만들 수
  없고, spawn 제한은 이 규칙의 특수 사례가 된다
- v0에 부분 move는 없다. 필드 접근은 빌림이고 결과도 빌린 값이다.
  affine 필드만 꺼내려면 부분 move 상태 추적이 필요한데 v0가 살 복잡도가 아니다

05를 자족적으로 다시 썼다. affinity의 뿌리가 capability라 자원 타입을 모듈
안에서 정의해야 검사기가 affine임을 유도할 수 있다. 외부 타입은 affine임을
증명할 수 없으므로 copyable로 본다.

이로써 fast path(L0 parse / L1 type·effect·capability·ownership)가 완성됐다.
cool check가 처음으로 성공을 선언한다 — 01~04, 06, 07이 exit 0으로 통과한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
2026-08-30 03:01:05 +09:00

116 lines
2.9 KiB
Plaintext

// 05. move / affinity 검사기가 거부해야 하는 코드
//
// 09, 10과 같은 이유로 외부 타입이 하나도 없다. affinity의 뿌리는 capability라
// 자원 타입을 이 파일에서 정의해야 검사기가 affine임을 알 수 있다.
// 외부 타입은 affine임을 증명할 수 없으므로 copyable로 취급된다.
pub capability File {
fn size() -> Int
}
pub capability Gateway {
fn refund(id: Int) effects {Gateway.refund}
}
pub capability Registry {
fn add(h: fn()) effects {Registry.add}
}
// 파일을 소비하는 함수: own 유표기
pub fn close(own f: File) effects {File.close}
// 빌리기만 하는 함수: 무표기
pub fn size_of(f: File) -> Int {
f.size()
}
// --- 통과해야 하는 것 ---
pub fn use_then_close(own f: File) effects {File.close} -> Int {
let n = size_of(f)
close(f)
n
}
// 양쪽 분기에서 소비하면 통과한다
pub fn both_branches_close(own f: File, c: Bool) effects {File.close} {
if c {
close(f)
} else {
close(f)
}
}
// 빌린 값을 다른 빌림 자리로 넘기는 것은 복제가 아니다
pub fn borrow_twice(f: File) -> Int {
size_of(f) + size_of(f)
}
// affine 값을 capture한 클로저는 affine fn이다
pub fn deferred_close(own f: File) -> affine fn() effects {File.close} {
fn() { close(f) }
}
// --- 여기서부터 전부 오류다 ---
// [E-move-after-move] affine 값의 이중 소비
pub fn double_close(own f: File) effects {File.close} {
close(f)
close(f)
}
// [E-move-join] 분기 병합은 보수적 합집합
pub fn conditional_close(own f: File, c: Bool) effects {File.close} {
if c {
close(f)
}
close(f)
}
// [E-use-escape] 빌린 값의 반환
pub fn leak_capability(pay: Gateway) -> Gateway {
pay
}
// [E-use-escape] 빌린 값의 저장
pub struct Holder {
pay: Gateway,
}
pub fn store_capability(pay: Gateway) -> Holder {
Holder { pay: pay }
}
// [E-use-escape] 빌린 값을 다른 함수에 소유로 넘긴다
pub fn give_away(f: File) effects {File.close} {
close(f)
}
// [E-use-escape] 빌린 값을 capture한 클로저를 own 자리에 넘긴다
pub fn register(own h: fn() effects {Gateway.refund}) effects {Registry.add}
pub fn escape_via_closure(pay: Gateway) effects {Registry.add} {
register(fn() { pay.refund(1) })
}
// [E-affinity-transitive] affine 필드를 가진 타입을 copyable로 선언
pub copyable struct Box {
f: File,
}
// [E-callable-affinity] affine 값을 capture한 클로저를 fn 위치에 반환
pub fn misuse_affine_closure(own f: File) -> fn() effects {File.close} {
fn() { close(f) }
}
// [E-closure-mut-capture] 클로저는 mut 바인딩을 capture할 수 없다
pub fn capture_mut(own f: File, pay: Gateway)
effects {File.close, Registry.add, Gateway.refund} {
let mut counter = 0
register(fn() {
counter = counter + 1
pay.refund(counter)
})
close(f)
}