정리에 바깥 권한이 따라붙으면 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
63 lines
2.2 KiB
Plaintext
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)
|
|
}
|