타입스크립트는 현재 5.1 입니다. 그리고 준비중인 다음 버전인 5.2에서 using 이라는 새로운 키워드가 지원될 예정입니다. 이 키워드는 tc39에 제안된 내용이고, 현재 Stage 3 단계로 다음 ECMAScript에서도 볼 수 있는 후보 상태입니다.
새로운 키워드는 기존 let, const 변수 선언 키워드에 기능이 추가된 형태이며, 이 키워드를 이용해 선언한 변수는 블록 스코프를 벗어날 때 자동으로 자원을 해제해 주는 새로운 기능입니다.
일반적인 자원 해제 코드 패턴
위 예제처럼 스트림, 버퍼 등 다양한 자원의 수명관리와 관련된 소프트웨어 개발 패턴이 존재하고, 다음과 같이 각 상황에 맞게 자원을 해제하는 메서드 호출이 필요합니다.
ECMAScript의 Iterator: iterator.return()
WHATWG의 Stream Reader: reader.releaseLock()
NodeJS의 파일 핸들러: handle.close()
Emscripten C++의 객체 핸들: Module._free(ptr), obj.delete(), Module.destroy(obj)
또한 throw 를 대응하기 위해 try { ... } finally { ...release }로 오류를 검사하는 것이 일반적입니다. 이는 코드가 길어지고, 반복적인 패턴이고, 개발자가 자원을 해제하지 못할 경우 메모리 누수로도 이어지는 좋지 않은 패턴이기도 합니다.
여러개의 자원을 다루는 코드
또한 자원은 새로운 자원으로 파생이 될 수 있으므로, 많은 자원을 다루게 된다면 위 예제 보다 더 코드는 깊어지고, 복잡하고, 이해하기 어려워집니다. 이러한 패턴을 단순화 하고, “자원의 해제는 변수가 존재하는 블록 스코프를 벗어날 때 만들어진 자원의 역순으로 자원을 해제”하기로 정한 것이 using 이라는 키워드입니다.
using 키워드 예제
자원 해제 메서드 Symbol.dispose
using 키워드를 사용하기 위해 자원 해제를 시키는 메서드가 필요합니다. 따라서 할당되는 자원에는 Symbol.dispose 라는 알려진 심볼을 사용해 자원을 해제하는 함수를 사용하게 됩니다.
Symbol.dispose 를 설명하기 위한 예제
자원을 생성하는데 Symbol.dispose 라는 이름의 속성에 자원 해제 함수를 넣었습니다. 이렇게 넣은 채로 using 키워드를 사용하게 되면, 자원이 있는 블록을 벗어날때 자동으로 resource[Symbol.dispose]?.() 를 실행하게 됩니다. 이로써 자원은 블록의 기능이 동작하는 동안에만 할당되며, 자동으로 해제되는 방식을 갖게됩니다.
try { ... } finally { ... } 로 바꾼 예제
비동기 자원 해제 await using
자원 해제 함수가 비동기 함수일 때 Symbol.asyncDispose 라는 알려진 심볼을 사용합니다. 이 심볼은 일반적인 using 키워드에서는 작동하지 않으며, await using 키워드에서만 동작하게 됩니다. 반대로, Symbol.dispose 는 두 키워드 모두 지원합니다.
async 가 아닌 await 이 붙은 이유는 위에서 설명드린 바와 같이 “자원 해제의 순서는 보장” 되어야 합니다. 비동기 자원 해제 또한 이 순서를 보장해야 하기 때문에 실제 비동기 함수는 기다려야 하는 상황이기 때문입니다.
비동기 using 키워드
for .. of 에서 사용할 수 있습니다.
이 키워드는 const, let 과 같은 변수 선언 키워드 입니다. 이러한 특성 때문에 기존 변수 선언 키워드를 사용할 수 있던 곳에서 사용이 가능합니다. 그 중 같은 await 키워드를 사용하는 사용처인 for .. of에서의 작동 방식 표입니다.
DisposableStack 컨테이너 객체
using 키워드와 함께 제안된 DisposableStack 과 비동기 버전인 AsyncDisposableStack 이 추가되었습니다. 이 객체들은 컨테이너에서 일회용 자원들이 해제될 수 있도록 보장해주는 객체입니다. 다음은 DisposableStack의 타입입니다.
DisposableStack 구현 타입
DisposableStack 사용방법
DisposableStack.adopt를 이용해 자원 해제 함수를 연결할 수 있습니다.
{ [Symbol.dispose]: () => { .. } } 가 구현이 안되어 있어도, DisposableStack.adopt를 이용해 using 을 이용한 자원 해제를 구현할 수 있습니다. 또한 비동기 자원해제는 AsyncDisposableStack을 이용해야 합니다.
DisposableStack.adopt() 를 이용한 자원 해제 구현
클래스 상속 관계의 생성자
자원 객체는 클래스에서도 사용이 가능합니다. 부모 클래스에서 자원 해제가 필요하고 자식 클래스에서 또한 자원 해제를 구현해야 할 때는 DisposableStack을 이용해 구현할 수 있습니다. 아래 예제는 제안서에 있던 예제를 재작성했습니다.
부모 클래스의 DisposableStack 구현 예제
자식 클래스의 DisposableStack 구현 예제
타입스크립트에서 사용하는 방법
현재 5.2의 개발은 많이 이뤄졌습니다. 그에따라 Beta 버전이 릴리즈 됐고, 마이크로소프트 공식 블로그에 버전에 대한 릴리즈 포스팅이 이뤄졌습니다. 그러면서 사용되는 방법이 나오게 되었는데 tsconfig.json 에서 compilerOptions.lib에 esnext.disposable 또는 esnext를 추가하면 사용이 가능합니다.
마무리
자원해제는 자바스크립트 뿐만 아니라 C++, 파이썬, 자바 등 다양한 언어에서 다뤄지는 프로그래밍 패턴입니다. 이를 정규화하기 위해 만들어진 이번 제안 내용은 C++에서 구현된 using 키워드, 파이썬에서 구현된 ExitStack 에서 영감받아 만들어진 DisposableStack 모두 기존 자바스크립트에서 볼 수 없던 내용이고, 다른 언어에서 시행착오를 겪은 내용들이기 때문에 잘만 정착된다면, Node.js 뿐만 아니라 브라우저의 그래픽, 오디오 등 다양한 자원이 안전하게 관리되는 환경이 구성될 수 있을거라 보여집니다.
저는 이와 같이 메모리 관리를 좀더 쉽게 다룰 수 있는 내용을 굉장히 좋아합니다. 앞으로도 이러한 내용이 더 추가 되었으면 합니다.