파일에 없던 셋을 압박했다: 자원 두 층(풀/lease), 반납이 파괴가 아님, 획득이
실패할 수 있음. 셋 다 설계가 버텼다.
D8 — 인자 안에서 소비와 사용을 섞을 수 없다.
release_then(db, pool, l, db.query(l, sql))가 거부된다. move 검사가 인자를
왼쪽부터 걷기 때문이고, 실제 평가 순서와 무관하다. 우회는 쉽지만 왜 안 되는지가
코드에서 안 보인다.
두 사례 비교:
naive careful 배수 match 중첩
Atomic 18 58 3.2 4단
DB Pool 25 58 2.3 1단
공통(2/2): 자원을 들면 ?를 한 번도 못 쓴다. "정리하고 결과를 실어나르는
도우미"를 양쪽이 각자 발명했다. 누수는 조용히 통과한다.
다른 점: 고통이 자원 개수가 아니라 정리의 균일성에 비례한다. 파일은 단계마다
정리가 달라 4단이 되고, 풀은 언제나 반납이라 1단으로 접힌다.
결정: resource/with를 넣는다. 다만 D4가 먼저다.
with l = db.acquire(pool)? { } 가 나갈 때 무엇을 부를지 알려면 자원만으로
정리를 표현할 수 있어야 하는데, 반납은 db.release(pool, l)이라 풀과 권한이
필요하다. 자원이 자기 정리를 스스로 선언하려면 수신자를 소비하는 메서드가
있어야 하고(own self), 지금은 own이 파라미터에만 붙는다.
파일 쪽도 같다 — Fs.close(own f)는 Fs 권한을 요구한다.
따라서 순서: D4 → resource/with → Mini Shell로 세 번째 점.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
59 lines
2.0 KiB
Plaintext
59 lines
2.0 KiB
Plaintext
// 제대로 반납하는 버전.
|
|
//
|
|
// Atomic Updater와 비교하는 것이 목적이다. 자원이 두 층이고, 반납이
|
|
// 파괴가 아니며, 획득이 실패할 수 있다 — 파일에는 셋 다 없었다.
|
|
|
|
import "../std-draft/db" as Db
|
|
|
|
// 한 요청. 성공하든 실패하든 lease를 반납한다.
|
|
pub fn load_user(db: Db.Db, pool: Db.Pool, sql: String)
|
|
effects {Db.acquire, Db.query, Db.release} -> Result[List[String], Db.DbError] {
|
|
match db.acquire(pool) {
|
|
Err(e) => Err(e),
|
|
// l을 소비하는 인자와 l을 쓰는 인자를 한 호출에 섞을 수 없다.
|
|
// move 검사는 인자를 왼쪽부터 걷기 때문이다 — 실제 평가 순서와
|
|
// 무관하다. 먼저 질의하고 결과를 넘긴다.
|
|
Ok(l) => {
|
|
let r = db.query(l, sql)
|
|
release_then(db, pool, l, r)
|
|
},
|
|
}
|
|
}
|
|
|
|
// 반납하고 원래 결과를 그대로 돌려준다.
|
|
// 반납 실패는 일부러 버린다 — 원래 결과가 더 중요하다.
|
|
//
|
|
// 파일 때와 다른 점: 정리가 실패 경로마다 다르지 않다. 성공이든 실패든
|
|
// 같은 일(반납)을 하므로 결과를 통째로 실어나르는 도우미 하나로 접힌다.
|
|
pub fn release_then(
|
|
db: Db.Db,
|
|
pool: Db.Pool,
|
|
own l: Db.Lease,
|
|
r: Result[List[String], Db.DbError],
|
|
) effects {Db.release} -> Result[List[String], Db.DbError] {
|
|
let _ = db.release(pool, l)
|
|
r
|
|
}
|
|
|
|
// 프로그램 하나. 풀을 열고 쓰고 닫는다.
|
|
pub fn run(db: Db.Db, url: String, sql: String)
|
|
effects {Db.pool, Db.acquire, Db.query, Db.release, Db.close_pool}
|
|
-> Result[List[String], Db.DbError] {
|
|
match db.pool(url, 8) {
|
|
Err(e) => Err(e),
|
|
Ok(pool) => {
|
|
let rows = load_user(db, pool, sql)
|
|
close_then(db, pool, rows)
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn close_then(
|
|
db: Db.Db,
|
|
own pool: Db.Pool,
|
|
r: Result[List[String], Db.DbError],
|
|
) effects {Db.close_pool} -> Result[List[String], Db.DbError] {
|
|
let _ = db.close_pool(pool)
|
|
r
|
|
}
|