|
Rust를 활용해 개발하는 많은 과제를 C 라이브러리와 연계해 만드는 경우가 많은데요. C와 Rust의 차이로 인해 생기는 메모리 값 유효성 오류를 대응해야 하는 경우가 많습니다. 이를 방지하기 위해서는 오류를 일일이 검증해야만 하는데요. 저와 동료들은 메모리 유효성 체크 작업을 자동화할 수 있는 Rust 라이브러리를 오픈소스로 공개했습니다. 저희가 개발한 오픈소스를 소개합니다. |
들어가며
삼성전자에 입사하고 보니, 저희 조직이 놀랄 만큼 다양한 주제를 연구하고 개발하고 있다는 것을 알 수 있었습니다. 그중 하나는 소프트웨어 운영 체제(Operating System)입니다. 이 분야에 관심이 조금이라도 있으신 분들은 C/C++ 기반의 리눅스를 대체할 수 있는 개발 언어로 주목받고 있는 Rust 를 많이 들어보셨을 텐데요. 회사 업무나 개인 취미로 Rust 를 활용해 다양한 프로그램들을 개발하면서 새로운 재미를 느끼셨을지도 모르겠습니다.
저와 동료들이 Rust를 운영 체제에 접목해서 개발을 시작할 때, 큰 제약 없이 활용 가능한 오픈소스들을 자연스럽게 찾게 되더군요. 그중 크레이트(Crate)라고 불리는 Rust의 공식 오픈소스 라이브러리를 찾았는데요. 개발에 도움이 될 만한 녀석[2][3][4][5]들은 닥치는 대로 적용해 보고, 에러를 만나고, 그걸 해결하려고 크레이트를 또 뒤지고, 또 다른 에러를 만나고, 이 지난한 과정을 거쳤지만 에러를 해결할 방법을 찾지 못하는 상황에 도달했습니다.
“그렇다면 크레이트 없이 해보자!” 고민하고 분석하고, 또 고민하여 마침내 해결책을 고안해서 적용할 때의 그 희열을 소프트웨어 개발자분들은 누구나 잘 아실 거예요. 이제 크레이트를 활용하여 우리가 찾은 새로운 해결책을 제약 없이 활용 가능한 라이선스로 만들어, 크레이트 오픈소스에 기여해보기로 했죠.
Rust 공식 로고[1]
우리가 맞닥뜨린 문제 상황
여러분들이 C로 개발된 3rd Party(서드파티) 라이브러리와 연계 동작하는 앱을 Rust로 개발하고 있다고 해보죠. 아마도 여러분들은 이런 코드들을 짰을 거예요.
자, 컴파일해 보죠. 라이브러리와 링크하고 Run.
이런, 당신이 짠 Rust 코드와 C 라이브러리가 Crash가 나네요. 디버깅해야죠. 그런데 아무것도 잘못된 것이 없었다면 골치가 아플 겁니다. 잠시만요. should_frobnicate 함수가 값 2를 반환하는 걸 제외하곤 이거 여전히 true 아닌가요?
Rust의 재미있는 특징 중 하나가 바로 "invalid value" 컨셉이죠[6]. 여러분 모두 알고 계시다시피, "bool" 타입은 0 또는 1만 허용되고, "enum" 타입은 유효한 variant 중 하나만 가능하고, 참조값들은 반드시 null이 아니어야 하고, 적절하게 할당되어야 하죠. 이러한 특징을 활용해서 컴파일러는 더 많은 최적화를 할 수 있고요.
반면에 예상하지 못한 에러들을 만들어 내기도 하는데요. 이와 같은 상황에서 should_frobnicate는 값 2를 반환하기로 했기 때문인데, 이와 연계 동작하는 C 라이브러리는 0 또는 1의 바이너리 값만 수용하는데, 3rd Party 라이브러리라 우리가 어떻게 할 수가 없죠. 이럴 경우, 해결책은 의외로 간단합니다. bool을 u8으로 바꾸면 되죠.
앱을 더 짜 볼까요? 이번엔 3rd Party 라이브러리가 몇가지 태깅된 유니온들을 정의하고 있음을 알게 되었습니다. 이렇게요.
위의 사례가 아주 전형적인 Rust의 "#[repr(x)] enum" [7]이 될 것입니다. 문제는, 이러한 enum 또한 Rust에서 받아들여야 하는 값들이며, 반드시 항상 유효한 값이어야 하는데, 3rd Party 라이브러리가 유효하지 않은 값을 뱉어내지 않는다 보장할 수 없다는 것이죠. 이럴 때 여러분들에게 두 가지 선택지가 있습니다.
* C 라이브러리의 enum을 있는 그대로 사용하여, Rust도 C 스타일로 개발한다.
* Rust의 enum을 새로 정의하고, 이를 검증할 수 있는 boilerplate를 만든다.
만약, 이와 같은 3rd Party 라이브러리가 위보다 훨씬 더 많은 유니온들을 정의하고 있다고 생각해 보세요. 생각만 해도 골치가 아프죠.
우리의 해결책
지금까지 Rust로 개발할 때, Rust 값이 유효한지 아닌지 안전하게 확인하기 위해 C로 개발된 3rd Party 라이브러리를 엉성하게 다루게 되는 사례를 설명해 보았는데요. 비단 위와 같은 사례만 있는 게 아닙니다. 예를 들어, 여러분이 Rust로 운영 체제를 개발하고 있고, userspace 로부터 변수들을 수용하는 경우처럼, 너무 일반적인 상황이면서 3rd Party에서 개발된 C 라이브러리를 사용하는 사례와 달리, 가정해야 할 게 아무것도 없다고 생각해 보죠.
여러분은 Rust의 enum과 struct들을 활용해서 즐겁게 개발을 하다가 (종종 오픈소스들도 많이 활용하고요), 어느 날 C 라이브러리와 연계하는 과정에서 표준 라이브러리나 기존에 있는 오픈소스 Crate에서는 좋은 해결책을 찾을 수 없는 상황이 생길 거예요. C의 “it's just memory"와 Rust의 "valid values only"의 컨셉 차이에서 발생하는 그런 문제들이요.
그래서 우리는 이러한 문제를 단번에 해결할 수 있는 코드를 자체 개발해 보았어요. 코드이미지를 봐주세요. (우리 코드 중 "isrepr" 라는 크레이트를 유심히 보세요[8]).
이 크레이트는 IsRepr 라는 절차적 매크로 (Procedural Macro)를 제공하는데요, 이를 통해 필요한 유효값 체크 코드를 만들어 준답니다. 임의의 메모리 저장에 유효하면서 동일한 레이아웃을 갖는 type을 만들기 위해, 여러분은 이 IsRepr 를 Repr<> type으로 래핑합니다. HasRepr 트레잇 (trait)을 기반으로 제공된 메소드(method)를 통해 여러분들에게 맞는 type을 변환할 수 있습니다. 번거롭지 않고 매우 쉽죠. 메모리 값 유효성 검증을 위해 일일이 검증 boilerplate를 짤 필요가 없습니다.
마지막으로, 현재 상황은?
이 크레이트는 원래 회사 안에서 Rust로 운영 체제를 개발하는 과제에서 고안해 활용해 왔어요. 수많은 enum들을 갖는 API가 있었고, 우린 이를 활용해 개발하는 과정에서 임의의, 신뢰할 수 없는 메모리의 유효성을 확인해야만 했었죠. Rust를 도입하면서 얻게 될 장점들은 포기하지 않고요. 정말 놀랍게도, 당시에는 이를 쉽게 처리할 수 있는 오픈소스 크레이트가 없었습니다. 그래서 이를 오픈소스로 릴리즈했죠. Isrepr가 다른 개발자에게 많은 도움이 되었으면 좋겠어요.
오픈소스에 관심 있는 개발자 여러분들은 잘 아실 거예요. 오픈소스 최초 릴리즈 버전이 완벽할 수는 없다는 걸요. 다만 여러분들께 약속드릴 수 있는 건, 이번에 우리가 공개한 크레이트는 분명 위와 같이 발생할 수 있는 C 라이브러리와 Rust 앱을 연계하는 개발 과정에서 큰 도움이 될 것으로 생각됩니다. 그리고 이번에 공개한 크레이트 첫 릴리즈를 시작으로, 오픈소스 개발에 관심 있는 여러분들과 함께 지속적으로 완성도를 높여서 수많은 개발자분들이 자유롭게 활용할 수 있는 Rust 개발자 툴로 함께 성장시켜 나가고 싶습니다.
