녹슬지 않는 튼튼함, Rust의 강력한 타입 시스템: TypeScript 경험을 넘어선 새로운 세계
저는 오랫동안 JavaScript를 주력으로 사용해 왔습니다.
그러다 TypeScript를 접하게 되었는데요, JavaScript에서 흔히 겪던 황당한 버그들, 예를 들어 "false"
대신 false
라고 잘못 써서 두 시간 동안 디버깅했던 경험들을 TypeScript가 해결해 주는 것을 보고 그 강력함에 감탄했었습니다.
마치 든든한 갑옷을 입은 기분이었죠.
그런데 최근 약 두 달 동안 Rust라는 언어를 배우면서 새로운 경험을 하고 있습니다.
Rust의 타입 시스템은 마치 TypeScript가 JavaScript에 비해 훨씬 강력한 것처럼, TypeScript를 한 단계 뛰어넘는 수준이라고 느껴집니다.
Rust를 경험하기 전에는 이런 세계가 있을 줄 상상조차 못했습니다.
코드가 실제로 어떤 타입인지 확실하게 보장해 주는 타입 시스템은 정말이지 놀랍고 즐거운 경험인데요.
TypeScript에서는 어떤 값이 특정 타입을 가진다고 주장해도, 실제로 그 타입이 맞는지 확신하기 어려울 때가 있습니다.
그 값을 얻기 위해 호출했던 수많은 함수 중 하나에서 as
assertion (타입 단언)을 사용했을 수도 있고, 그 assertion이 틀렸다면 타입은 유효하지 않게 되죠.
이렇게 잘못된 타입이 코드 깊숙한 곳에 숨어 있으면 전염병처럼 퍼져나가지만, 우리는 쉽게 알아차릴 수 없습니다.
Rust와 비교했을 때 TypeScript의 타입 추론은 약한 편입니다.
이 때문에 as
assertion을 강제로 사용해야 하는 상황이 자주 발생하는데요, 이는 굉장히 좋지 않습니다.
왜냐하면 코드에 잘못된 타입이 유입될 가능성을 스스로 만드는 것이고, 이는 곧 문제로 이어질 수 있기 때문입니다.
흔히들 백엔드에서 JavaScript를 사용하는 것을 비웃을 때, 저는 TypeScript가 훌륭한 해결책이라고 생각했습니다.
하지만 프로그램이 정말로 올바르게 작동할 것이라고 확신하고 싶다면 TypeScript를 사용할 때 더욱 주의해야 합니다.
값은 특정 타입이라고 주장하지만, 실제로는 그렇지 않을 수 있기 때문입니다.
어떤 의미에서 as
assertion을 사용하는 것은 Rust에서 unsafe
블록을 사용하는 것과 비슷합니다.
컴파일러가 스스로 추론할 수 없는 어떤 불변성(invariant)을 프로그램이 유지할 것이라고 약속하는 것이죠.
Rust 커뮤니티는 unsafe
블록에 대해 왜 특정 불변성이 유지되는지 명확하게 문서화하는 것을 매우 중요하게 생각합니다.
하지만 as
assertion은 그렇게 취급되지 않는 경우가 많습니다. 깊이 생각하지 않고 습관처럼 사용되곤 하는데, 이는 분명한 문제인데요.
저에게 있어 강력한 타입 시스템은 언어를 선택하는 데 가장 중요한 요소 중 하나입니다. 타입 시스템을 통해 우리는 코드가 아닌 방식으로 프로그램의 목적을 표현할 수 있습니다.
Rust와 Haskell 덕분에 타입 시스템에 대한 새로운 눈을 뜨게 되어 정말 감사하게 생각합니다.
또한 TypeScript 컴파일러를 만들기 위해 노력하신 모든 분들께도 감사드립니다.
완벽과는 거리가 있지만, 왜 그렇게 될 수밖에 없었는지 이해합니다. 결국 TypeScript는 오래된 JavaScript로 컴파일되니까요.
TypeScript는 JavaScript 위에 얹어진 필수적인 보호막이라고 할 수 있습니다.
하지만 언젠가 TypeScript를 더 이상 사용하지 않아도 되는 미래, Rust 프레임워크인 Dioxus가 Next.js처럼 대중화될 수 있는 날이 오기를 간절히 바랍니다.
타입 시스템, 강력함과 견고함은 다르다?
흥미로운 댓글들을 살펴보면서 타입 시스템에 대한 더 깊은 이해를 얻을 수 있었는데요.
한 유저님은 타입 시스템에서 "강력함(powerful)"과 "견고함(strong)"은 매우 다른 의미를 가질 수 있다고 지적했습니다.
강력한 타입 시스템은 다양한 타입 제약을 표현할 수 있는 능력을 의미하지만, 견고한 타입 시스템은 쉽게 "깨지지 않는", 즉 우회하거나 속일 수 없는 시스템을 말한다고 합니다.
사실 TypeScript의 타입 시스템이 강력하기 때문에 완벽하게 견고하지 못하다는 의견도 있었는데요, 이는 any
타입, as
캐스팅, 그리고 @ts-ignore
와 같은 기능들이 JavaScript와의 통합을 위해 의도적으로 설계되었기 때문이라고 합니다.
물론 TypeScript도 실용적인 이유로 모든 타입 제약을 철저히 검사하지 않는 부분이 있습니다.
예를 들어 배열에 인덱스로 접근할 때나 특정 상황에서 함수를 정의할 때 인수 간의 공변성(covariance)이 제대로 검사되지 않을 수 있다는 의견도 있었습니다.
하지만 이는 noUncheckedIndexedAccess
설정을 통해 어느 정도 해결할 수 있다고 하네요.
구조적 타이핑 vs 명목적 타이핑
TypeScript의 강력함 중 하나는 구조적 타이핑(structural typing)을 지원한다는 점입니다.
이는 타입의 이름이 아니라 "모양", 즉 포함하고 있는 속성과 메서드가 동일하면 같은 타입으로 취급하는 방식인데요.
반대로 Rust와 같은 언어는 명목적 타이핑(nominal typing)을 사용하며, 이름이 같아야 같은 타입으로 인정합니다.
TypeScript의 구조적 타이핑은 유연성을 제공하지만, 때로는 예상치 못한 버그를 유발할 수도 있다는 우려도 있습니다.
서로 다른 이름을 가진 인터페이스가 우연히 같은 "모양"을 가져서 혼용될 수 있고, 나중에 하나의 인터페이스가 변경되면 다른 곳에서 예기치 않은 문제가 발생할 수 있기 때문입니다.
물론 TypeScript에서도 명목적 타이핑과 유사하게 동작하도록 만들 수 있지만, 근본적으로 구조적 타이핑이라는 점은 변하지 않습니다.
TypeScript의 as
는 Rust의 unsafe
와 같다?
글쓴이는 TypeScript의 as
assertion을 Rust의 unsafe
블록과 유사하다고 언급했는데요, 이 부분에 대한 다양한 의견이 있었습니다.
as
를 사용하는 것에 대해 좀 더 신중해야 한다는 점에는 많은 사람들이 동의했지만, 모든 프로젝트에서 as
사용에 대한 문서화를 강제하는 린터(linter)를 사용하고 있다는 의견도 있었습니다.
결국 두 언어는 서로 다른 목표와 타입 시스템을 가지고 있으며, TypeScript의 유연성은 JavaScript와의 호환성을 위한 의도적인 선택이라는 점을 기억해야 합니다.
완벽하게 엄격한 타입 시스템을 가진 새로운 언어를 JavaScript로 컴파일하는 것은 기술적으로 가능하지만, 기존 JavaScript 코드와의 호환성을 잃게 되어 대중적인 인기를 얻기 어려울 수 있습니다.
타입에 대한 깊은 고민
댓글들을 통해 다양한 언어들의 타입 시스템에 대한 이야기가 나왔습니다.
Haskell, Scala, Kotlin 등 강력한 타입 시스템을 자랑하는 언어들이 언급되었고, 각 언어의 특징과 장단점에 대한 토론이 이어졌습니다.
특히 Rust의 소유권(ownership) 시스템과 Borrow Checker는 메모리 안전성을 보장하는 강력한 기능으로, 다른 언어에서는 찾아보기 힘든 장점이라고 할 수 있습니다.
물론 Java나 C++도 훌륭한 타입 시스템을 가지고 있지만, Rust가 제공하는 수준의 엄격함과 안전성은 독보적이라는 의견이 많았습니다.
결론
이번 글과 댓글들을 통해 TypeScript와 Rust의 타입 시스템에 대한 깊이 있는 논의를 할 수 있었습니다.
TypeScript는 JavaScript 생태계에서 강력한 도구이지만, Rust는 더욱 엄격하고 안전한 타입 시스템을 통해 새로운 수준의 프로그래밍 경험을 제공한다는 것을 알 수 있었습니다.
어떤 언어를 선택할지는 프로젝트의 요구 사항, 개발팀의 숙련도, 그리고 추구하는 가치에 따라 달라지겠지만, 타입 시스템에 대한 깊은 이해는 더욱 안정적이고 예측 가능한 소프트웨어를 만드는 데 필수적이라는 것을 다시 한번 깨닫게 되었습니다.
앞으로도 다양한 언어의 타입 시스템을 탐구하며 더 나은 프로그래밍을 위한 길을 찾아나가야겠습니다.
'Rust' 카테고리의 다른 글
인터뷰 번역) Tauri vs 다른 Rust GUI 프레임워크: Arboretum 개발자의 이야기 (1) | 2025.02.09 |
---|---|
Rust 'unsafe' 제대로 파헤치기: 개발자가 알아야 할 모든 것 (0) | 2025.02.09 |
Rust 초보자의 경험: OOP 한계와 컴파일 시간 문제점 완벽 분석 (0) | 2025.02.09 |
리눅스 커널과 Rust의 갈등: Hector Martin 사퇴 이후의 미래와 과제 (0) | 2025.02.09 |
Rust로 간단한 CLI 유효성 검사 도구 만들기 (0) | 2024.11.24 |