러스트로 크로스 플랫폼과 GUI 프로그래밍 시작하기

December 7, 2023 김백기 조회수 12,879

안녕하세요. VD 사업부 S/W Platform Lab의 김백기입니다. 오늘 기술 블로그 포스팅에서는 러스트(Rust)로 크로스 플랫폼(cross-platform)과 GUI 프로그래밍을 다루는 방법을 설명하고자 합니다.

크로스 플랫폼과 GUI 프로그래밍

크로스 플랫폼 개발은 여러 다양한 운영체제나 기기에서 동작할 수 있는 소프트웨어를 개발하는 접근 방식입니다. 크로스 플랫폼은 한 번의 개발로 다양한 기기와 운영체제에서 애플리케이션을 실행할 수 있도록 지원하기 때문에 보다 많은 사용자에게 서비스를 제공할 수 있다는 큰 장점이 있습니다.


이전에는 어떤 기능을 추가/삭제하거나 변경해야 할 때 지원하는 플랫폼마다 동일 작업을 해줘야만 가능했습니다. 이는 유지보수 측면에서 비효율적이고, 지원하는 플랫폼이 늘어날수록 작업량이 늘어납니다. 또, 똑같은 기능을 수행했을 때 플랫폼마다 동작 결과가 달라질 수 있습니다. 플랫폼마다 서로 다른 사람이 구현하기 때문에 발생하는 문제인데요. 서로 다른 동작으로 인해 사용자에게 불편을 초래할 수 있다는 점에서 이를 해결할 새로운 해결 방식인 크로스 플랫폼이 필요하게 되었습니다.


이 포스팅에서는 러스트로 크로스 플랫폼을 구현하는 방법을 설명하고 Android Native Development Kit(이하 Android NDK)을 활용하여 Android 환경에서 러스트로 네이티브 앱을 개발하는 방법을 알아봅니다. 또한 러스트로 GUI 프로그래밍하는 방법을 소개하고 간단한 샘플까지 작성해 보겠습니다.



크로스 플랫폼 소개



그림 1. 크로스 플랫폼 구성도


러스트를 사용하면 개발자는 단 한 번의 코드 작성으로 다양한 기기에서 구동 가능한 애플리케이션을 개발할 수 있습니다. 러스트 툴체인을 통해 arm, x64, wasm 등으로 컴파일된 코드는 스마트폰, 윈도우, 브라우저 등에서 작동할 수 있으며 이를 통해 크로스 플랫폼 개발의 용이성과 고성능, 웹 어셈블리 지원 등의 혜택을 쉽게 체감할 수 있습니다.


샘플을 작성하기 전에 준비할 것들이 있습니다. 러스트로 크로스 플랫폼을 개발하기 위해서는 먼저 툴체인을 설치해야 합니다. 러스트는 Tier1, Tier2, Tier3 등으로 툴체인 레벨을 구분하고 있습니다. Tier1은 검증되고 러스트 지원이 확실한 타겟만을 실은 레벨입니다. 그 외에는 Tier2나 Tier3에 배정됩니다.


허나 Tier2만 해도, 상용화가 가능할 정도의 높은 품질을 의미하기 때문에 원하는 툴체인이 Tier2에 있다고 하더라도 충분히 사용 가능합니다. 아래는 Tier1툴체인 목록입니다.

Tier1 툴체인 목록

Arm64용 Hello World 개발하기

arm64용 Hello World 프로젝트를 개발하기 위해서는 먼저 rustup 명령어를 사용하여 arm64용 툴체인을 설치해야 합니다.



그다음엔 arm64용 gcc를 설치합니다.



설치 후, 사용자 디렉토리의 .cargo/config 파일을 수정하여 링커를 등록해야 합니다.



이제 간단한 프로젝트 하나를 생성합니다.



아래 명령어를 사용하여 arm64로 빌드합니다.



file 명령어를 사용하여 arm64로 정상 빌드되었는지 확인해 보겠습니다.



해당 파일을 라즈베리파이나 qemu와 같은 시스템에 배포하여 arm64 기기에서 구동할 수 있습니다.



1. Android NDK 개발


Android NDK는 앱의 성능을 향상시키기 위해 C, C++ 등의 'native' 언어를 사용하여 부분 또는 전체를 작성할 수 있게 해주는 도구 세트입니다. 일반적으로 Android 앱은 Java 또는 Kotlin과 같은 언어를 사용하여 개발되지만, 더 높은 수준의 성능이 필요한 경우에는 NDK를 사용하여 native 코드를 작성할 수 있습니다.


NDK를 사용하면 특히 계산 집약적인 작업을 더 빨리 처리할 수 있습니다. 예를 들어, 게임, 물리 시뮬레이션, 신호 처리, AI, 그래픽 처리 등의 애플리케이션에서 흔히 사용됩니다. 그러나 NDK를 사용하면 애플리케이션의 복잡성이 증가하고 디버깅이 어려워질 수 있으므로, 성능 향상이 반드시 필요한 경우에만 사용하는 것이 좋습니다.


또한, Android NDK는 Java Native Interface(이하 JNI)를 사용하여 Java와 native 코드 간 상호 작용을 가능하게 합니다. 이는 Java로 작성된 앱이 native 메서드를 호출할 수 있도록 하며, 반대로 native 코드가 Java 메서드를 호출하거나 Java 객체를 조작할 수 있습니다.



그림 2. NDK 아키텍처


러스트는 다른 native 언어와 마찬가지로 Android NDK와 함께 사용할 수 있습니다. 러스트의 안전성과 성능을 활용하여 Android 앱을 개발하는 경우도 있으며 Google은 Android Framework의 상당 부분을 러스트로 개발하고 있습니다.



그림 3. Android의 러스트 지원


2. 개발 환경 구성


Android NDK를 개발하기 위해서는 개발 환경 구성이 필요합니다. 먼저 Google 개발자 사이트에서 Android Studio를 다운로드해 설치합니다.



그림 4. Android Studio


본 예제에서는 리눅스를 기준으로 설명합니다. 설치 파일을 다운로드하고 압축을 해제합니다. 설치가 끝났으면 아래 명령어를 실행하여 Android Studio를 실행합니다.



만약 윈도우를 사용 중이시면 윈도우 버전의 설치 파일을 다운로드한 후 프로그램 > Android Studio를 실행합니다. Studio가 정상적으로 구동되면 메뉴 상단의 Tools에서 SDK Manager를 선택합니다.

그러면 아래와 같은 설정창이 뜹니다.



그림 5. SDK Manager 설정


여기서 NDK에 필요한 요소들을 선택합니다. 아래 항목은 필수적으로 설치되어야 합니다.


  • NDK(Side by side)
  • Android SDK Command-line Tools
  • CMake


여기까지 완료했다면 러스트에 Android 관련 툴체인을 등록해야 합니다. rustup 명령어를 사용하여 Android 관련 target을 등록합니다.



target 등록이 끝났으면 사용자 디렉토리의 .cargo/config 파일을 열어 아래 내용을 추가합니다.



이제 NDK를 사용할 준비가 되었습니다.



3. 간단한 NDK 샘플 만들기


NDK를 사용하기 위해서는 라이브러리 크레이트를 생성해야 합니다.



Cargo.toml 파일을 열어 JNI 크레이트를 추가합니다.



그리고 Android에서 호출할 함수들을 추가합니다. 함수명은 JNI 명명법을 따라야 합니다. 예를 들어 com.example.myapplication.MainActivitiy에서 helloRust라는 러스트 함수를 호출하기 위해서는 러스트의 함수명은 Java_com_example_myapplication_MainActivity_helloRust가 되어야 합니다.


러스트에서 JNI 사용하기



Java에서 사용하는 자료형은 러스트에서 바로 사용할 수 없기에 JNIEnv 내의 마샬링(marshaling) 함수들을 사용하여 직접 변환해야 합니다. 여기서는 get_string() 함수를 사용하여 Java 문자열을 획득했습니다. 이제 빌드를 해보겠습니다. 아래 명령어를 사용하여 arm64, arm, x64, x86용 NDK 라이브러리를 빌드하겠습니다.



이제 러스트 부분은 준비가 완료되었습니다.


Android 샘플 프로젝트를 생성합니다. Android Studio를 열고 간단한 샘플 프로젝트를 생성합니다. C/C++ 프로젝트를 선택하는 것이 좋지만 아무 프로젝트를 선택해도 괜찮습니다. 메인 코드를 작성합니다. 패키지명과 Activity명은 러스트 NDK 쪽에 등록한 이름과 완전히 일치해야 합니다.


Android에서 러스트 함수 호출



빌드가 끝났으면 생성된 so를 Android 프로젝트 app/src/main/jniLibs/에 복사합니다. 복사 경로는 아래와 같습니다.



NDK 라이브러리 배포 경로


실행하면 아래와 같이 성공적으로 러스트 함수가 호출된 것을 확인할 수 있습니다.



그림 6. Android에서 러스트 코드 실행 결과



4. Java와 Rust의 성능 비교


Java는 가상 머신 상에서 실행되기 때문에 때때로 네이티브 코드에 비해 성능이 떨어질 수 있습니다. 특히 CPU 집약적인 작업에서 이러한 성능 저하가 두드러집니다. NDK를 사용하면 더 낮은 수준의 언어로 코드를 작성할 수 있으므로, 성능이 중요한 영역에서 효율성을 높일 수 있습니다. 그래서 NDK를 사용하는 주요 이유 중 하나는 Java의 상대적으로 느린 성능을 개선하는 것입니다.


뿐만 아니라 러스트는 메모리 관리에 있어 더 효율적인 방식을 제공합니다. Java의 Garbage Collection(가비지 컬렉션)과 달리, 러스트는 컴파일 시점에서 메모리 안전성을 검사합니다. 그 결과, 런타임 과정에서 메모리를 더 효율적으로 사용하게 됩니다.


아래는 언어별 성능 벤치마크를 제공하는 benchmarksgame-team1 의 Java와 러스트의 성능 비교 자료입니다. 대부분 항목에서 러스트는 Java의 성능을 압도합니다.



러스트와 Java의 성능 벤치마크


차트로 나타내면 아래와 같습니다.



그림 7. 러스트와 Java의 성능 비교


정리하면 NDK를 사용하는 이유는 Java의 느린 성능을 개선하고, 네이티브 코드의 재사용성을 높이기 위함입니다. Java와 러스트의 성능 비교를 통해, 서로의 장단점을 이해하면 시스템의 특정 영역에 적합한 언어와 도구를 선택하는 데 도움이 될 수 있습니다.


1https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html

GUI 프로그래밍

러스트는 시스템 프로그래밍 언어로 설계되었으며, 성능, 안정성, 병렬 처리 등에 중점을 둡니다. 그러나 GUI(Graphical User Interface) 개발에 대한 직접적인 지원이 러스트를 사용하는 주된 이유는 아닙니다.


그럼에도 불구하고, 러스트로 GUI 애플리케이션을 개발할 수 있는 여러 서드파티 라이브러리가 있습니다. 몇 가지 예로는 iced, gtk-rs, conrod와 같은 라이브러리가 있으며, 이를 사용하면 러스트로 GUI 애플리케이션을 만들 수 있습니다. 러스트의 안전성과 성능이 GUI 개발에 긍정적인 영향을 미칠 수 있습니다. 특히, 복잡한 시스템 또는 고성능이 필요한 그래픽 애플리케이션에서 이러한 특징들이 중요할 수 있습니다.


하지만, 다른 언어들과 비교했을 때, GUI 개발에 특화된 라이브러리나 도구의 지원이 상대적으로 적을 수 있으므로, 프로젝트의 요구 사항과 개발팀의 경험을 고려하여 결정하는 것이 좋습니다.

러스트의 공식 사이트인 ‘Are we GUI Yet?’를 방문하면, 다양한 GUI 툴킷을 확인할 수 있습니다. GUI 툴킷은 데스크톱이나 모바일 등 다양한 플랫폼에서 러스트로 GUI를 개발할 수 있도록 지원합니다. 사용자의 요구와 선호에 맞게 선택할 수 있는 라이브러리와 예제, 그리고 커뮤니티에서 만든 튜토리얼들도 함께 제공되므로, 러스트로 GUI 개발을 시작하려는 개발자들에게 큰 도움이 될 수 있습니다.



그림 8. Are we GUI Yet? 홈페이지


1. ICED



그림 9. ICED 로고


ICED는 러스트로 작성된 선언적인 GUI 라이브러리로, 데스크톱 애플리케이션 개발에 특화되어 있습니다. 빠르고 안정적인 러스트 언어의 장점을 바탕으로, 사용자 경험이 뛰어난 현대적인 인터페이스를 구축하는 데 중점을 두고 있습니다. 특히나 React와 같이 선언적 프로그래밍(declarative programming)을 지원하여 개발자는 사용자 인터페이스만 선언하면 프레임워크가 다른 동작들을 처리하도록 구현할 수 있습니다. 뿐만 아니라 크로스 플랫폼을 지원하여 리눅스나 윈도우와 같은 다양한 디바이스에서 동작하도록 구성할 수 있습니다.


ICED는 tour라고 하는 간단한 데모를 지원합니다. tour는 아래와 같이 github에서 다운로드할 수 있습니다.



실행하면 아래와 같이 Welcome! 화면이 나타납니다.



그림 10. ICED 실행 결과


2. egui



그림 11. egui 샘플


egui는 러스트로 작성된 신속하고 사용하기 쉬운 GUI 라이브러리로, 웹과 데스크톱 애플리케이션을 위한 풍부한 사용자 인터페이스를 제공합니다.

간단한 설계와 사용자 친화적인 API를 갖춘 egui는 러스트 개발자들의 GUI 프로그래밍을 손쉽게 만들어 줍니다.


egui는 즉시 모드(immediate mode)라는 기능을 제공합니다. 이를 통해 사용자 인터페이스를 선언하는 코드가 더 간결하고 유연하며, 런타임 동안 상태의 변경에 더욱 민첩하게 반응할 수 있습니다.

뿐만 아니라 활발한 커뮤니티와 잘 정리된 문서를 통해 지속적인 개선과 지원이 이루어지고 있습니다.


egui는 빠르게 변화하는 사용자 인터페이스를 구현하고 싶은 개발자들에게 이상적인 선택이 될 수 있으며, 다양한 애플리케이션에서 쉽게 통합할 수 있습니다. egui를 사용하기 위해서는 아래와 같은 라이브러리를 설치해야 합니다.



설치가 끝나면 github에서 egui를 다운로드합니다.



아래 명령어로 demo를 실행합니다.




그림 12. egui 실행 결과



3.gtk-rs


gtk-rs는 러스트 프로그래밍 언어를 위한 GTK+ 바인딩의 모음입니다. GTK+는 GIMP 툴킷의 약자로, 크로스 플랫폼 GUI(Graphical User Interface)를 만드는 데 사용되는 라이브러리 중 하나입니다. gtk-rs는 러스트 개발자들이 이러한 라이브러리를 활용하여 데스크톱 애플리케이션을 개발할 수 있도록 돕습니다.


gtk-rs는 GTK+라는 유명한 라이브러리를 기반으로 하기 때문에 다른 GUI 툴킷에 비해 많은 레퍼런스가 있습니다. 이에 따라, 다른 툴킷 대비 커뮤니티 지원이 막강합니다. 문서화나 버그 수정, 기능 개선도 빠르게 이루어지고 있습니다.



그림 13. gtk-rs로 개발된 앱


gtk-rs를 사용하기 위해서는 gtk를 설치해야 합니다.



github에서 gtk-rs를 다운로드합니다.



아래 명령어를 사용하여 example의 notepad를 구동합니다.



실행 결과는 아래와 같습니다.



그림 14. egui 실행 결과


요약

크로스 플랫폼은 한 번의 개발로 다양한 기기와 운영체제에서 구동할 수 있기 때문에 보다 많은 사용자에게 서비스를 제공할 수 있는 장점이 있습니다. 지금까지 Android에서 러스트를 효과적으로 활용하기 위한 NDK 개발 방법과 성능이 중요한 영역에서 러스트가 어떤 도움이 되는지 살펴보았습니다. 또한 GUI 툴킷에 대해 알아보고 최근 각광받고 있는 ICED, egui, gtk-rs 등을 소개했습니다. 러스트가 GUI 측면에서 부족하다는 의견이 많지만, 이러한 툴킷을 사용하면 GUI에 더 쉽게 접근할 수 있을 것으로 생각합니다.


이상으로 러스트로 크로스 플랫폼과 GUI 프로그래밍을 다루는 방법을 실제 예제를 함께 따라가 보며 배워보았습니다.


VD 사업부 S/W Platform Lab의 김백기였습니다.

감사합니다.




저자

김백기

S/W Platform Lab(VD)

이메일 문의하기