Files
coollang/dogfoods/atomic-update/careful.cool
T
coolguyandClaude Opus 5 28001d9c1d own self: capability 메서드가 수신자를 소비한다 (D4)
정리에 바깥 권한이 따라붙으면 with가 자원만 보고 무엇을 부를지 알 수 없다
(D9). own self로 자원이 자기 정리를 스스로 선언한다.

- 문법: cap_method의 첫 자리에만 온다. 좌인수분해로 LL(1) 유지 (충돌 0건)
- 빌림에는 적을 말이 없다 — self만 적는 문장은 존재하지 않는다
- own self는 interface hash에 들어간다 (호출 측 move 결과가 바뀌므로)

같이 막은 구멍 둘:
- 중첩 수신자(h.lease.release())가 시그니처를 못 찾아 조용히 빌림이 됐다.
  own self가 생기면 그것은 자원을 두 번 소비할 수 있다는 뜻이다
- 메서드 이름만으로 시그니처를 찾아 이름이 겹치면 진단이 틀렸다.
  수신자의 선언된 타입을 알면 그 capability 안에서만 찾는다

그리고 테스트 하네스의 결함:
요약과 exit 1이 파일 184줄에 있었다. 최상위 let ()은 파일 순서대로 돌므로
그 뒤 1500줄의 검사는 FAIL을 찍고도 종료 코드가 0이었다 — 실패한 테스트를
커밋할 수 있었다. 요약을 파일 끝으로 옮겼다.

개밥 먹기 네 파일을 2판으로 다시 썼다. 도우미의 시그니처가 정리의 진짜
의존을 말하게 됐다.

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

63 lines
2.2 KiB
Plaintext

// 제대로 정리하는 버전.
//
// naive.cool과 같은 일을 하지만 모든 실패 경로에서 핸들을 닫고 임시 파일을
// 지운다. 두 파일의 차이가 이 개밥 먹기의 산출물이다.
//
// ※ 2판. `own self` 뒤에 다시 썼다. 도우미들이 더 이상 fs를 들고 다니지
// 않는다 — 닫는 데 필요한 것이 핸들 자신뿐이기 때문이다.
import "../std-draft/fs" as Fs
import "cool.dev/std/string" as String
pub fn update(fs: Fs.Fs, path: String, contents: String)
effects {Fs.create, WriteFile.write, WriteFile.sync, WriteFile.close,
Fs.rename, Fs.remove}
-> Result[Unit, Fs.IoError] {
let tmp = String.concat(path, ".tmp")
match fs.create(tmp) {
Err(e) => Err(e),
Ok(f) => finish(fs, f, tmp, path, contents),
}
}
// 핸들을 얻은 뒤. 여기서부터 모든 실패 경로가 f를 닫고 tmp를 지워야 한다.
// ? 를 쓸 수 없다 — 조기 반환이 정리를 건너뛰기 때문이다.
pub fn finish(
fs: Fs.Fs,
own f: Fs.WriteFile,
tmp: String,
path: String,
contents: String,
) effects {WriteFile.write, WriteFile.sync, WriteFile.close, Fs.rename, Fs.remove}
-> Result[Unit, Fs.IoError] {
match f.write(contents) {
Err(e) => abandon(fs, f, tmp, e),
Ok(_) => match f.sync() {
Err(e) => abandon(fs, f, tmp, e),
Ok(_) => match f.close() {
Err(e) => discard(fs, tmp, e),
Ok(_) => match fs.rename(tmp, path) {
Err(e) => discard(fs, tmp, e),
Ok(_) => Ok(unit),
},
},
},
}
}
// 핸들을 아직 들고 있는 실패. 닫고 지운다.
// 정리 중의 실패는 일부러 버린다 — 원래 오류가 더 중요하다.
// let _ = 가 버린다는 사실을 코드에 보이게 한다.
pub fn abandon(fs: Fs.Fs, own f: Fs.WriteFile, tmp: String, e: Fs.IoError)
effects {WriteFile.close, Fs.remove} -> Result[Unit, Fs.IoError] {
let _ = f.close()
discard(fs, tmp, e)
}
// 핸들은 이미 없다. 임시 파일만 지운다.
pub fn discard(fs: Fs.Fs, tmp: String, e: Fs.IoError)
effects {Fs.remove} -> Result[Unit, Fs.IoError] {
let _ = fs.remove(tmp)
Err(e)
}