명시적 리소스 관리(Explicit Resource Management) 제안이 처분 가능성(disposability)이라는 새로운 ‘컬러’ 축을 도입하면서 발생하는 모호성과 업그레이드 문제를 살펴보고, Effection 같은 구조적 리소스 수명 모델이 이를 어떻게 완화하는지 논한다.
Sun Feb 15 2026 명시적 리소스 관리(Explicit Resource Management) 제안은 어딘가 익숙한 문제에 부딪힙니다.
이는 TypeScript 이전의 JavaScript가 겪었던 문제와 같은 부류입니다.
JavaScript는 유연성을 줍니다. TypeScript는 보장을 줍니다.
TypeScript는 타입을 통해 JavaScript를 강화하여, 프로그램에서 데이터가 어떻게 흐르는지 추론할 수 있게 해줍니다. 함수가 무엇을 기대하는지 더 이상 추측하지 않습니다. 어떤 값이 돌아오는지 이해하려고 구현을 뒤져볼 필요도 없습니다. 타입 시스템이 주변(ambient)의 진실의 원천이 됩니다.
명시적 리소스 관리는 비슷한 모호함을 도입하는데, 대상은 처분 가능성(disposability) 입니다.
어떤 값이 주어졌을 때, 어떻게 알 수 있을까요:
using을 써야 하나요?await using을 써야 하나요?값 자체만으로는 알 수 없습니다.
그래서 다음 중 하나를 해야 합니다:
마지막 방법은 특히 많은 것을 드러냅니다. TypeScript는 어떤 값이 Symbol.dispose 또는 Symbol.asyncDispose를 구현하지 않았다는 사실을 사후에 알려줍니다. 하지만 그건 처분 가능성을 선제적으로 추론하는 게 아니라, 반응적으로 발견하고 있다는 뜻입니다.
이는 고전적인 async 컬러 문제를 떠올리게 합니다.
async 함수에서는 non-async 문맥에서 async 함수를 호출할 수 없고, 그렇게 하려면 코드의 색(color)을 바꿔야 합니다. 그 “색”은 위로 전파됩니다.
명시적 리소스 관리에서는 이제 새로운 컬러 축을 도입합니다: 처분 가능성(disposability).
어떤 값은 처분 가능하고, 어떤 값은 그렇지 않습니다. 어떤 값은 await using이 필요하고, 어떤 값은 using이 필요합니다. 그 색이 호출 지점(call site)으로 새어 올라옵니다.
이제는 신경 써야 합니다.
또 다른 미묘한 문제가 있습니다.
처분 가능한 리소스를 반환하는 라이브러리가 있다고 상상해봅시다. 당신은 그것을 올바르게 using으로 감쌉니다.
나중에 라이브러리가 업데이트됩니다. 그 리소스는 더 이상 처분 가능하지 않습니다. 아마 구현이 바뀌었을 겁니다.
갑자기 프로그램이 컴파일되지 않습니다.
로직은 바뀌지 않았습니다. 의도도 바뀌지 않았습니다. 하지만 값의 색이 바뀐 것입니다.
라이브러리의 작은 변화가 애플리케이션 전반의 대규모 리팩터링으로 이어집니다. 리소스의 색이 호출 그래프(call graph)로 새어 나왔기 때문입니다.
using을 쓰면 되지 않나?”이렇게 묻는 사람도 있을 겁니다: 그냥 모든 것을 using으로 감싸고 끝내면 되지 않냐고요?
하지만 처분 가능성은 보편적이지 않습니다.
그것은 조건적이며, 옵트인이고, 구현에 의해 정의됩니다.
즉, 올바르게 사용하는 부담이 호출자에게 있다는 뜻입니다.
이는 다른 생태계에서 사람들이 구조적 동시성(structured concurrency)을 받아들이게 만든 것과 같은 종류의 사용성(ergonomics) 세금입니다.
JavaScript 제안은 Rust의 소유권(ownership) 모델에서 영감을 받은 가벼운 메커니즘입니다.
하지만 여전히 호출자 주도(caller-driven)입니다.
Rust에서는 무언가가 정리가 필요한지 추측할 필요가 없습니다. 소유권과 라이프타임은 타입 시스템의 일부입니다. 컴파일러가 이를 강제합니다. 정리는 자동이며, 구조적으로(scope by construction) 스코프에 묶여 있습니다.
JavaScript에서는 빌림 검사(borrow checking)를 뒤늦게 덧씌울 수 없습니다.
하지만 호출자에게서 책임을 옮길 수는 있습니다.
Effection은 같은 부류의 문제를 다루지만, 다른 방향에서 접근합니다.
호출자가 처분을 명시적으로 관리하도록 요구하는 대신, 리소스를 구조화된 실행 컨텍스트에 스코프화합니다.
함수가 실행될 때, 그 함수가 생성한 모든 리소스는 스코프를 벗어나기 전에 정리됨이 보장됩니다.
호출자는 처분을 관리하지 않습니다. 함수가 정리를 소유합니다. 런타임이 그 경계를 강제합니다.
이는 호출 지점에서 처분 가능성 컬러를 완전히 제거합니다.
using 예제 리팩터링다음은 MDN 스타일의 리소스입니다:
class Resource {
value = Math.random();
#isDisposed = false;
getValue() {
if (this.#isDisposed) {
throw new Error("Resource is disposed");
}
return this.value;
}
[Symbol.dispose]() {
this.#isDisposed = true;
console.log("Resource disposed");
}
}
명시적 리소스 관리를 사용하면, 호출자는 사용을 using으로 감싸야 합니다.
Effection을 사용하면, 수명주기 관리를 캡슐화할 수 있습니다:
import { resource } from "effection";
function acquireResource() {
return resource(function* (provide) {
let value;
try {
if (Math.random() < 0.5) {
value = null;
} else {
value = new Resource();
}
yield* provide(value);
} finally {
value?.[Symbol.dispose]();
}
});
}
// ...
const resource = yield * acquireResource();
console.log(resource?.getValue());
무엇이 바뀌었는지 보세요.
호출자는 다음을 하지 않습니다:
using과 await using 중에서 선택하기acquireResource가 설정(setup)과 해제(teardown)를 내부에서 처리합니다.
정리는 구조화되어 있습니다. 보장됩니다. 스코프에 묶여 있습니다.
명시적 리소스 관리는 우리에게 새로운 도구를 제공합니다.
하지만 더 깊은 질문은 이것입니다:
리소스 수명은 호출자가 기억해야 하는 것일까요?
아니면 구조에 의해 강제되어야 할까요?
TypeScript는 타입을 명시적으로 만들어서 JavaScript의 데이터 흐름 모호성을 해결했습니다.
Effection 같은 구조적 동시성 라이브러리는 스코프를 강제 경계로 만들어 수명주기 모호성을 해결하려고 시도합니다.
하나는 타입을 더합니다. 다른 하나는 구조를 더합니다.
둘 다 같은 목표를 겨냥합니다: 추측을 없애는 것.