Files
coollang/dogfoods/std-draft/fs.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

71 lines
3.4 KiB
Plaintext

// 파일 시스템 — 개밥 먹기용 초안.
//
// 이 파일은 구현이 아니라 계약이다. 런타임이 구현한다고 가정하고, coollang이
// 이 일을 표현할 수 있는지만 본다. 실행은 안 되지만 타입·effect·capability·
// 소유권 검사는 전부 진짜로 돈다.
//
// 규율: 여기 적는 것은 지금 런타임이 가진 권한으로 구현 가능해야 하고,
// effect를 전부 선언해야 하며, 없는 언어 기능에 기대면 안 된다. 없는 기능이
// 필요하다는 게 드러나면 그것이 발견이지 지름길이 아니다.
// ------------------------------------------------------------------
// 실패는 무엇인가
//
// 여기서 Result와 crash의 선을 긋는다. 기준은 "호출자가 대처할 수 있는가"다.
// 대처할 수 있다 → Result
// 프로그램이 틀렸다 → crash
//
// 그래서 아래는 전부 Result다. 파일이 없는 것도, 권한이 없는 것도, 디스크가
// 가득 찬 것도 프로그램의 결함이 아니다 — 세상의 상태다.
// ------------------------------------------------------------------
pub enum IoError {
NotFound(String),
Denied(String),
Exists(String),
NoSpace(String),
// 나머지. 런타임이 분류하지 못한 것들
Other(String),
}
// 열린 쓰기 핸들.
//
// capability로 선언하는 이유가 둘이다.
// 1. 이것은 실제로 권한이다 — 이 파일에 쓸 수 있는 권한
// 2. capability는 affinity의 뿌리이므로 복제되지 않는다
//
// ※ 2판. `own self`가 생긴 뒤 다시 썼다. 1판에서는 핸들에 메서드가 하나도
// 없었다 — close가 핸들을 소비해야 하는데 수신자를 소비할 방법이 없어서
// 전부 Fs의 메서드로 밀어냈다. 이제 핸들이 자기 메서드를 갖는다.
//
// 그러면서 권한의 모양이 바뀌었다. 쓰기·동기화·닫기에 더 이상 Fs가 필요
// 없다. 핸들만 넘기면 그 파일에 쓸 권한만 넘어가고 파일 시스템 전체 권한은
// 넘어가지 않는다 — 권한이 좁아진 채로 전달된다. 1판에서는 쓰게 하려면
// Fs를 통째로 줘야 했다.
pub capability WriteFile {
// 핸들을 빌린다. 여러 번 쓸 수 있다.
fn write(s: String) effects {WriteFile.write} -> Result[Unit, IoError]
// 디스크까지 내려간다. 이게 없으면 rename이 원자적이어도 내용이 없을 수 있다.
fn sync() effects {WriteFile.sync} -> Result[Unit, IoError]
// 수신자를 소비한다. 두 번 닫을 수 없다 — own self가 그것을 강제한다.
// 이 시그니처에 Fs가 없다는 것이 요점이다. 정리에 필요한 것이 자원
// 자신뿐이어야 `with`가 자원만 보고 무엇을 부를지 알 수 있다 (D9).
fn close(own self) effects {WriteFile.close} -> Result[Unit, IoError]
}
// 파일 시스템에 손댈 권한. 이름으로 하는 일이 여기 남는다 —
// 경로는 파일 시스템 전체에 대한 이름이므로 전체 권한이 필요하다.
pub capability Fs {
// 새로 만든다. 이미 있으면 자른다.
fn create(path: String) effects {Fs.create} -> Result[WriteFile, IoError]
// 같은 파일 시스템 안에서 원자적이다.
fn rename(from: String, to: String) effects {Fs.rename} -> Result[Unit, IoError]
fn remove(path: String) effects {Fs.remove} -> Result[Unit, IoError]
fn read(path: String) effects {Fs.read} -> Result[String, IoError]
}