Rust이야기를 하려면 C언어와 메모리에 대해 이야기 해야 한다.
C언어는 어셈블리어의 지긋지긋한 문법을 대체하기 위해 나타났다. 덕분에 수십만줄이 되는 코드를 몇 천줄로 짤 수 있게 되었고 이런 엄청난 생산성으로 인해 사람들은 점차 C언어로 개발하기 시작한다.
C언어는 단순히 어셈블리어를 추상화하여 문법을 깔끔하게 만든게 끝이기 때문에 사실상 어셈블리어의 동작과 크게 다르지 않다. 그렇기 때문에 지존 슈퍼 짱짱 개발자들은 C언어 코드를 보면 어셈블리어 코드가 보인다고 한다.
C언어의 메모리에 대해 이야기 해보자. RAM 이야기다. 동적 메모리 할당을 위해서는 스택이 아닌 힙에 데이터를 저장한다. 왜냐? 스택은 겁내 빠른 대신 크기 조정을 못한다. 반면 힙은 크기 조정을 할 수 있는데 느리다. 그렇기 때문에 동적 메모리 할당에서 ‘동적’이라는 특징 때문에 메모리를 힙에 저장해야 한다.
근데 문제는 이 힙에 저장된 데이터를 개발자가 알아서 정리를 해줘야 한다. 메모리 할당/해제 이야기다. 메모리를 할당 해놓고 해제하지 않으면 메모리 누수가 발생한다. 이거 겁나 귀찮다는 것이다. 동적 메모리 할당을 했으면 free.free…..free함수를 미친듯이 호출해야 한다. 조금이라도 이 함수를 호출하는 걸 까먹는다? 어느순간 메모리가 줄줄줄 새고 프로그램이 죽어있다.
한국에는 Java개발자들이 정말 많은 것 같다. - 나는 별로 이런 현상에 대해 좋아하지 않는다. 하하 - Java는 GC(Garbage collection)라는 걸 쓰는데 이건 앞서말한 문제점을 해결한다. GC라는 친구가 메모리 상에서 더 이상 안 쓰는 놈들을 다 찾아서 메모리를 해제 시켜버린다. 그럼 GC가 뭐가 문제길래 이렇게 말을 하고 있냐? 얘는 좀 지맘대로 구는 성격이 있다. 메모리를 수집하는 시간을 알기 어렵다. 또, 메모리를 없앤다는 놈이 불필요하게 많은 메모리를 사용하게 되고 GC실행으로 인해 오버헤드도 발생한다.
C언어 같이 메모리를 개발자가 직접 관리할 수 있는 언어는 언매니지드 언어라 하고 C#, Java같이 메모리를 관리 기능을 언어가 알아서 해주는 언어는 매니지드 언어라고 한다.
언매니지드 언어는 보통 개발자가 직접 메모리 할당을 다 해줘야 해서 귀찮고 메모리 누수 문제 등으로 인해 이는 분명히 안정성에 문제가 있다. 반면 매니지드 언어는 메모리를 언어가 직접 관리해 주는 대신 이로 인한 오버헤드가 발생한다.
이런 문제점 말고도 null pointer라는 문제도 존재한다. C언어는 뭐 말할 것도 없다. 그냥 메모리 해제시켜놓고 값 접근 하면 null뜬다. Java같은 언어는 포인터 대신 참조를 쓴다. 포인터랑 참조의 차이는 메모리를 개발자가 관리할 수 있냐 없냐다. 사실 그 차이를 제외하고서 null이 있다는 점에서 문제는 동일하다. null인데 값 접근하면 에러 터지는 거지 않나.
이런 null이나 메모리 관련 문제점들은 프로그래밍 언어들의 숙명이었다. kotlin을 쓰든 Swift나 Go를 쓰든 null… nil…None 다 있다.
Rust가 나오기 전까지…
이제 Rust이야기를 해보자. Rust는 모질라 재단에서 만든 언어로 매우 잘 설계된 언어이다.
Rust의 변수는 좀 특별하다. let키워드로 변수 선언을 하는데 default가 상수이다. 좀 웃긴 게 이름은 ‘변수’라고 지어놓고 상수가 default라니… mut키워드를 써서 변수를 진짜 변수 역할를 할 수 있도록 만들 수 있다. 또, Rust는 null이 없다. 대신 Option이라는 enum을 사용한다. 상속도 없다. struct로 trait을 사용한다.
답은 소유권과 생명주기의 개념. 그리고 러스트의 철학에 있다.
Rust의 각각의 값은 owner라고 불리는 변수를 갖고 있다. 한 값은 하나의 owner만 가질 수 있고 owner가 scope밖으로 벗어나면 그 값은 버려진다. C언어로 치면 free함수를 호출하는 거라 보면 된다. scope는 중괄호로 감싸진 코드블럭의 영역이라고 보면된다.
이렇게 되면 각각의 값은 명확한 생명주기를 갖게 된다. owner의 생명주기는 scope에 따라 결정되고 그 owner를 참조(reference)하는 변수들은 owner의 생명주기를 따라가게 된다.
자… 여기까지 들었을때 이게 뭔 소용이냐? 도대체 이게 뭐가 Rust의 장점이냐고 할 수 있다.
Rust는 생명주기를 컴파일 시간에 알 수 있다… 으잉? 그렇기 때문에 생명주기가 끝난 변수를 참조하게 되면 에러가 발생한다. ‘컴파일 시간에’ 뭐? ‘컴파일 시간’ Rust는 컴파일 시간에 다 잡아준다. null의 의미를 다시 생각해보자. null을 Rust언어로 생각해보면 ‘생명주기가 끝난 값’이라고 할 수 있다. Rust는 이 null을 컴파일 시간에 잡는 다는 말이다.
null문제를 소유권과 생명주기를 사용하여 해결했는데 이 때문에 언어 문법 자체도 되게 간결해진다. Kotlin, swift를 보면 null(nil) 핸들링을 위해 ‘??’, ‘?:’, ‘if let’, ‘guard let’, ‘?.’등 드럽게 많은 문법들을 외워야 하고 Java는 애초에 그런 문법 차원에서 null을 핸들링할 수 있는 게 없기 때문에 Optional이라는 타입으로 null을 핸들링한다. 사실 Java의 Optional타입은 null을 핸들링할 수 있는 기능만 제공하지만 이 Optional만으로 null인지 아닌지 확실하게 보장하는 것은 또 불가능하다. 이건 그냥 언어 자체의 문제인 것이다. (C# 은 있는데…)
또 이러한 특징은 안정성 측면에서 진짜 미쳤다. 이렇기 때문에 의료계나 군사를 다루는 그런 특정한 분야에서는 의무적으로 Rust를 사용해야 할지도 모른다.
null도 없는데 Exception(언어에 따라 종종 Error라고도 불림)도 없다. 그렇기 때문에 try-catch 문이 없다. Result라는 enum타입을 제공하는데 이걸로 처리한다.
이게 뭔 말이냐. 언어가 지맘대로(암묵적으로) 값을 바꾸거나 하지 않는다는 말이다. JavaSciprt에서는 1 + ‘a’ (아마 ‘1a’)같은 문법을 허용한다. C언어에서는 int랑 float이랑 더할 수 있다. Rust는 이걸(종종 말도 안 되는 문법이라고 할 수 있는)허용하지 않는다. 타입 변환에는 to_~~어쩌구 함수를 쓰든지 as키워드를 쓰든지 해야 한다. 또 Rust 2018부터는 dyn키워드를 앞에 붙여야 trait(Java의 interface 비슷한 것)를 사용할 수 있다 쉽게 말해 개발자가 외워야 하는 쓸데 없는 문법이 줄어든다는 말이다.
당연히 장점만 있는 것은 아니다. Rust의 문법이 어렵다.
일단 타입이 겁내많다 u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64, &str, &’static str, Vec, String, OsStr, OsString… 등등. - 느슨했던 프로그래밍씬에 긴장감을 준다는 점에서 나는 졸라 재미있게 공부할 수 있었다. 오랜만에 타입을 제대로 활용해서 코딩하는 느낌을 받음. -
이런 Rust의 어려운 문법적 특성때문에 생산성이 별로 좋지 않을 수 있다. 이는 빠르게 구현해서 내보내야하는 프론트 쪽이 아니라면 크게 문제될 건 없어보인다.
또, 소유권과 생명주기는 프로그래머들에게 생소한 개념이다. 변수의 default가 const인 점이 어려울 수 있다.
아직 Rust로 만들어진 그럴만한 큰 프로젝트가 없다. Rust는 성장 중인 언어이고 커뮤니티가 제대로 활성화 돼 있지 않다.
일단 언어자체로 보면 뭐든 다 할 수 있다. AI, 게임, 웹, 앱(은 잘 모르겠음), OS, 시스템, 서버, 블록 체인 …. 등등 다 만들 수 있긴하다. 성능도 개 빵빵하게 만들 수 있다.
하지만 다까도 말했듯 커뮤니티가 제대로 형성 돼 있지 않아 많은 사람들이 그냥 취미로 배우고 있는 수준에 그친다. 현업에서는 성능이 부족한 부분은 C, Go, Rust로 때운다는 경우도 몇번 들어보긴 했다… (어? 성능 부족해? 그럼 Go. 어? 성능 부족해? 그럼 Rust)
나는 주관적으로 언어의 기본동작이 좋은 언어가 가장 좋은 언어라고 생각한다. 상업적 측면을 떠나서 기본이 좋은 언어들은 프로그래밍 언어의 성장의 지향점이 되어야 한다. Rust는 설계가 정말 잘 되어 있다.
이건 글을 쓰다가 방금 만들어진 나의 따끈따끈한 이론이다. 남자를 이루는 3대 요소가 무엇인가. 생각 + 힘 + 규율. Rust를 사용한다는 것은 생각할 줄 안 다는 것이다. 명확한 사용 이유 없이 Rust쓰는 건 멍청한 짓이라고 생각한다. Rust는 강력한 타입, 소유권과 생명주기 시스템. null과 exception따위는 개나줘버린 미친 문법. Rust만의 특별한 규율이다. Rust는 이 모든 걸 다 탑재하고 있고 이러한 Rust의 특징들은 강력한 힘을 불러 일으키며 그 자체로 충분한 매력이 있다.
“어떤 프로그래밍 언어 쓰세요?”
라고 누군가 물었을때
“저는 Python합니다.”
보다는
“저는 Rust 합니다.”가 좀 더 강력해보이지 않은가?
아래는 Rust로 만든 교내 동아리를 위한 디스코드 PM 봇이다. 3일정도 걸렸다.
TODO관리, 매주 스프린트 알림, 기타 DB나 설정 기능이 있다.
데이터베이스 따위는 따로 없고 데이터베이스 용 채널을 하나 파서 거기 있는 디스코드 메세지를 Json 파싱해서 NoSQL 데이터베이스 마냥 사용한다. 프로젝트 할때는 주로 iOS개발을 하는데 DB까지 구축할 시간이 없었다.
https://github.com/bestswlkh0310/mowgli-v3
솔직히 Rust를 진짜 사용해 본 건 3~4일 밖에 안됐다.
그럼에도 불구하고 기본적인 문법을 어느정도 알고 개발할 수 있었는데 이것은 Rust의 기본 동작이 좋기 때문이다. Rust는 문법이 어렵지 외울 건 없고 언어가 간결하다. 기본 동작이 좋은 언어는 예측 가능하며 몇몇 개념을 알면 연역적 사고를 할 수 있다.
또 나는 Python, Java, Kotlin, Swift, C, C#, TypeScript 등등… 수많은 언어를 사용했고 또 프로젝트에도 직접 다 적용해봤지만 Rust만큼 나에게 신앙심을 가득 채워준 언어는 처음이다. 정말 Rust가 잘 됐으면 좋겠다. 이건 프로그래밍의 근간을 바꿀 언어가 분명하다.
글 잘 읽었습니다!
Exception이 없는 이유는 수학에서 예외 발생을 다루지 않고 함수의 결과로 모든 것을 처리하는 것처럼, 러스트에서도 오류나 부적절한 조건은 Result, Option 타입을 통해 안정성을 높이고, 함수의 반환 값만으로 처리를 합니다!
러스트에 관심이 있다면, 이미 들어오셨을 지 모르겠지만 https://rust-kr.org/ 한국 러스트 사용자 디스코드 들어오는 걸 추천해요!