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

57 lines
2.8 KiB
Plaintext

// 데이터베이스 연결 풀 — 개밥 먹기용 초안.
//
// 세 층이 있고 수명을 관리하는 주체가 각각 다르다.
// Conn 실제 연결. 풀이 소유한다. 사용자는 존재도 모른다
// Pool 연결 묶음. 프로그램 수명. 사용자가 만들고 닫는다
// Lease 빌린 한 자리. 요청 하나 수명. 반드시 반납해야 한다
//
// 사용자가 관리하는 것은 연결의 수명이 아니라 lease의 수명이다.
// 그래서 반납은 "파괴"가 아니다 — 연결은 풀로 돌아간다.
//
// ※ 2판. `own self`가 생긴 뒤 다시 썼다. 전에는 정리가 전부 Db의 메서드라
// 반납 한 번에 풀과 권한을 같이 들고 다녀야 했다. 이제 자원이 자기
// 정리를 스스로 선언한다.
// ※ List[String]의 List는 내장 타입이라 import가 필요 없다. 모듈 List를
// 가져오는 것은 그 타입에 딸린 함수(len, map...)를 쓸 때뿐이다.
pub enum DbError {
// 연결 자체가 안 된다
Unreachable(String),
// 풀이 가득 찼고 기다릴 수 없다. 호출자가 대처할 수 있다 — 재시도든 포기든
Exhausted,
// 질의가 틀렸다. 프로그램의 결함에 가깝지만 런타임 값에서 오므로 Result다
BadQuery(String),
Other(String),
}
// 빌린 한 자리. 반드시 반납해야 한다.
// ※ 언어는 아직 그 "반드시"를 강제하지 못한다 (D3). `with`가 그 자리다.
pub capability Lease {
// 질의한다. 수신자를 빌린다 — 한 lease로 여러 번 질의할 수 있다.
fn query(sql: String) effects {Lease.query} -> Result[List[String], DbError]
// 반납한다. 연결은 풀로 돌아간다 — 파괴가 아니다.
// 수신자를 소비하므로 두 번 반납할 수 없다. 그리고 이 시그니처에는
// 풀도 Db 권한도 없다 — 반납에 필요한 것은 lease 자신뿐이다.
// 그것이 `with`가 성립하기 위한 조건이다 (D9).
fn release(own self) effects {Lease.release} -> Result[Unit, DbError]
}
// 풀. capability이므로 affine이다 — 복제되지 않는다.
pub capability Pool {
// 한 자리를 빌린다. 없으면 Exhausted — 기다리지 않는다.
// 기다리는 형태(timeout)는 취소를 정해야 해서 미룬다.
fn acquire() effects {Pool.acquire} -> Result[Lease, DbError]
// 풀을 닫는다. 안에 있는 연결이 전부 정리된다.
// 아직 빌려나간 lease가 있으면? — 아래 발견 참고.
fn close(own self) effects {Pool.close} -> Result[Unit, DbError]
}
// 데이터베이스에 손댈 권한. 풀을 만들 수 있는 유일한 출처다.
pub capability Db {
// 풀을 연다. size는 최대 동시 연결 수.
fn pool(url: String, size: Int) effects {Db.pool} -> Result[Pool, DbError]
}