Rust와 GPUI로 데스크톱 시간 앱 만들기: 초보자의 여정
Source: Dev.to
러스트 시간 역설: 개발자의 이야기
나는 Rust로 간단한 world‑clock 앱을 만들기로 했습니다. 6주가 지난 뒤에야 드디어 작동시켰는데—다섯 개 도시의 현재 시간을 표시합니다. 아이러니하게도, 시간을 표시하는 데 너무 많은 시간을 투자한 나머지 시간 자체가 멈춘 듯했습니다.
이 프로젝트가 가르치는 내용
마스터하게 될 핵심 Rust 개념
- Ownership & Borrowing – 모든 타임존 카드는 자체 상태를 관리하는
Entity입니다. - Trait Implementation – UI 컴포넌트를 위한 사용자 정의
Render트레이트. - Type Safety – 런타임 타임존 오류를 방지하는 컴파일 타임 보장.
- Lifetimes –
&mut Context와 윈도우 참조에 대한 이해.
GPUI 프레임워크 기본
impl IntoElement를 이용한 컴포넌트 아키텍처.Entity와Context를 통한 상태 관리.cx.observe_window_bounds()를 사용한 반응형 업데이트.- 플렉스박스 스타일 API를 활용한 레이아웃 시스템.
프로젝트 빌드 및 실행 방법
필수 조건
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Verify installation
rustc --version
cargo --version
프로젝트 설정
새 프로젝트 만들기
cargo new world-time-app
cd world-time-app
Cargo.toml에 의존성 추가
[dependencies]
chrono = { version = "0.4" }
gpui = "0.2"
gpui-component = "0.4.0-preview1"
src/main.rs에 코드를 복사합니다.- 빌드 및 실행:
cargo run --release
일반적인 빌드 문제 및 해결책
-
문제: “레지스트리에서
gpui를 찾을 수 없습니다”
해결책: GPUI는 crates.io가 아니라 Git을 통해서만 사용할 수 있습니다. 위와 같이 Git 의존성을 사용하세요. -
문제: 컴파일에 10분 이상 걸림
해결책: 첫 번째 빌드는 느립니다(의존성 다운로드/컴파일). 이후 빌드는 Cargo 캐시를 사용합니다.--release옵션은 최종 빌드 시에만 사용하세요. -
문제: Linux에서 창이 나타나지 않음
해결책: 필요한 시스템 라이브러리를 설치합니다:# Ubuntu/Debian sudo apt-get install libxkbcommon-dev libwayland-dev # Fedora sudo dnf install libxkbcommon-devel wayland-devel
코드 아키텍처 분석
WorldTime 엔티티
pub struct WorldTime {
name: String,
time: String, // HH:MM format
diff_hours: i32, // offset from home
is_home: bool,
timezone_id: String,
}
Key learning: Rust의 구조체는 데이터를 소유하거나 명시적으로 빌려야 합니다. String(소유)과 &str(빌림) 중 선택하는 것이 첫 번째 큰 결정이었습니다.
컴포넌트 렌더링 패턴
impl Render for WorldTime {
fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement
}
Key learning: &mut self 매개변수가 며칠 동안 저를 혼란스럽게 했습니다. 가변 참조를 통해 컴포넌트는 렌더링 사이클 동안 상태를 업데이트할 수 있습니다.
시간 업데이트
if now.duration_since(self.last_update).as_secs() >= 60 {
for city in &self.cities {
city.update(cx, |city, _cx| {
city.update_time();
});
}
}
Key learning: Entity의 update() 메서드는 컨텍스트와 클로저를 받습니다. 클로저는 &mut T(엔티티 내부 상태에 대한 가변 접근)와 컨텍스트를 전달받아 GPUI의 소유권 모델 내에서 안전하게 상태를 변형할 수 있게 합니다.
러스트 초보자를 위한: 내가 알았으면 좋았던 것들
1‑2주차: 컴파일러는 당신의 선생님
도전 – “왜 이게 컴파일되지 않을까?!”
// ❌ This fails
let time = WorldTime::new(...);
render(time);
use_time_again(time); // ERROR: value moved
// ✅ This works
let time = WorldTime::new(...);
render(&time);
use_time_again(&time); // Borrowing, not moving
교훈: 러스트의 오류 메시지는 상세합니다. 주의 깊게 읽으세요—대부분 해결 방법을 포함하고 있습니다.
3‑4주차: 라이프타임은 두렵지 않다
도전 – 트레이트 바운드에서 'a 이해하기.
돌파 순간 – 라이프타임은 컴파일러에게 “이 참조는 이 기간 동안 유효합니다”라고 알려주는 것뿐입니다. 컴파일러가 대부분을 추론합니다.
// GPUI handles this complexity:
fn render(&mut self, _window: &mut Window, _cx: &mut Context)
// You don’t write the lifetimes manually
5‑6주차: Borrow Checker와 싸우기
악명 높은 오류
error[E0502]: cannot borrow `self.cities` as mutable because it is also borrowed as immutable
내 해결책 – 로직을 별도 함수로 분리합니다. Borrow Checker는 스코프 단위로 동작합니다:
// Instead of doing everything in one method:
fn render(&mut self) {
let cities = &self.cities; // Immutable borrow
self.update_all(); // ❌ Mutable borrow while immutable exists
}
// Split into logical units:
fn render(&mut self) {
self.update_if_needed(); // Mutable borrow ends here
let cities = &self.cities; // Now we can borrow immutably
}
나를 구한 디버깅 전략
- 프린트 디버깅은 여전히 유효합니다
dbg!(&self.time); // Rust's debug macro
println!("City: {}, Time: {}", self.name, self.time);
Lessons Learned
1. Keep the Boilerplate Minimal
fn main() {
// Minimal setup
let app = App::new();
app.run();
}
2. Start Simple, Add Complexity
My first version just showed one city. Then two. Then five.
Each step compiled before moving forward.
3. Use cargo check Constantly
cargo check # Fast syntax checking without building
cargo clippy # Linting suggestions
cargo fmt # Auto‑formatting
4. Read the Compiler Suggestions
Rust’s compiler often suggests fixes:
help: consider borrowing here
|
5 | render(&time);
| +
FAQ: 흔히 묻는 러스트 학습자 질문
왜 러스트는 배우기 어려운가?
러스트는 나중에 충돌을 디버깅하는 대신 메모리 관리를 미리 생각하도록 강요합니다. 학습 곡선이 가파르지만 전체 버그 종류(사용‑후‑해제, 데이터 레이스, 널 포인터 역참조)를 예방합니다.
러스트에서 생산성을 갖추려면 얼마나 걸릴까?
- 2–3주 기본 문법
- 2–3개월 편안한 개발
이 프로젝트(6주)는 실제 문제를 해결해야 했기 때문에 어떤 튜토리얼보다도 많은 것을 가르쳐 주었다.
먼저 C++를 배워야 할까?
아니다. 러스트의 소유권 시스템은 독특합니다. C++ 습관은 오히려 러스트를 더 어렵게 만들 수 있습니다. 바로 러스트부터 시작하세요.
코드가 컴파일되지 않으면 어떻게 할까?
초보자에게는 정상 – 나는 전체 시간의 약 70 %를 컴파일러 오류와 싸우는 데 보냈다. 각 오류는 무언가를 가르쳐 줍니다.
언제 .clone()을 사용해야 할까?
여러 개의 소유 복사가 필요할 때. 클론은 성능 비용이 들지만 개발을 진행하게 해줍니다. 나중에 최적화한다:
// Quick solution:
let city_copy = city.clone();
// Optimized later:
let city_ref = &city;
언제 &, &mut, 혹은 참조 없이 사용해야 할까?
| Symbol | Meaning |
|---|---|
& | 읽기 전용 대여 (가장 일반적) |
&mut | 독점 쓰기 접근 |
| (none) | 소유권 이전 (값을 소모) |
&부터 시작하고, 수정이 필요하면 mut를 추가하며, 필요할 때만 소유권을 이전한다.
이 프로젝트의 성능 인사이트
메모리 사용량
- 컴파일된 바이너리: ~4 MB (최적화된 릴리스 모드)
- 런타임 메모리: 5개의 타임존 카드에 대해 ~8 MB
- 가비지 컬렉터 오버헤드 없음
시작 시간
- 콜드 스타트: M1 Mac에서 ~200 ms
- 핫 스타트: ~50 ms
업데이트 효율성
60초마다 업데이트 검사가 매 렌더 프레임에서 실행되지만 필요할 때만 재계산됩니다—Rust의 제로 코스트 추상화를 보여줍니다.
실제 도움이 된 자료
필수 학습 경로
- The Rust Book (공식) – 1‑10장 먼저 읽기
- Rust by Example – 실습 코드 스니펫
- Rustlings – 올바르게 작성하면 컴파일되는 인터랙티브 연습문제
- This project – 개념의 실제 적용
막혔을 때
- Rust Users Forum – 초보자를 위한 친절한 커뮤니티
- r/rust subreddit – 매일 질문 스레드
- Rust Discord – 실시간 도움
- Compiler errors – 진지하게, 두 번 읽어보세요
GPUI 전용 자료
- Zed GitHub repo – 소스 코드 예시
crates/gpui/examples/에 있는 GPUI 예시- Zed의 자체 코드베이스 – 프로덕션 GPUI 사용
Source: …
가공되지 않은 진실: 나의 시간 투자
시간 분배 (예상)
| Activity | Hours |
|---|---|
| 문서 / 튜토리얼 읽기 | 40 |
| 컴파일러 오류와 싸우기 | 60 |
| 이해 후 다시 작성하기 | 15 |
| 실제 기능 코딩 | 10 |
| “왜 창이 보이지 않지?” | 8 |
| Total | 133 |
그럴 가치가 있었나요?
네.
- 두 번째 Rust 프로젝트는 3 일 걸렸습니다.
- 세 번째는 몇 시간 안에 끝났습니다.
첫 주에 몇 시간을 잡아먹던 컴파일러 오류가 이제는 몇 분이면 해결됩니다. Rust의 난이도는 초기 단계에 집중돼 있습니다; 학습 비용을 한 번만 지불하면 영원히 혜택을 누릴 수 있습니다: segfault 없음, 데이터 레이스 없음, 예기치 않은 런타임 패닉 없음.
다음 단계: 프로젝트 확장하기
초보자 친화적인 추가 기능
- 도시를 더 추가하기 (패턴을 그대로 복사)
- 색상 / 스타일 변경 (RGB 값을 수정)
- 날짜 표시 추가 (
time을 확장하여 날짜 포함)
중급 과제
- 설정 파일에서 도시를 읽기 (파일 I/O 학습)
- 도시를 동적으로 추가 / 제거 (UI 상태 관리 학습)
- 아날로그 시계 표시 (그리기 API 학습)
고급 기능
- 시스템 트레이 통합
- 서머타임 처리
- 도시당 다중 시간대 지원
- 네트워크 시간 동기화
실제 교훈: 고난을 받아들여라
Rust 코드가 첫 시도에 바로 컴파일된다면, 당신은 배우는 것이 아니라 베끼는 것입니다.
빌려주기 검사기 오류, 이해되지 않는 수명 주석, “왜 이걸 이동시킬 수 없지?” 라는 디버깅에 쏟는 시간—그것이 바로 학습 과정입니다.
내 시간 앱은 이제 완벽히 동작합니다. 메모리 누수도 없고, 레이스 컨디션도 없으며, 정의되지 않은 동작도 없습니다. 컴파일러가 첫 실행 전에 이를 보장했습니다.
내가 “잃은” 시간은 실제로는 잃은 것이 아니라, 프로덕션에서 당신의 시간을 존중하는 언어를 이해하는 데 투자한 시간입니다.
프로젝트 저장소
직접 빌드해보고 싶나요? 전체 코드는 위 문서에 있습니다. 간단한 버전(한 도시)부터 시작해 컴파일을 성공시키고, 점차 복잡성을 추가하세요.
기억하세요: 모든 Rust 개발자는 컴파일 오류에 몇 시간을 보냈습니다. 초보자와 전문가의 차이는 전문가가 더 많은 실수를 했다는 점입니다.
Rust 1.75+, GPUI(Zed 프레임워크), 결단력, 그리고 빌림 검사기와의 건강한 관계로 구축되었습니다.
“컴파일러는 여러분에게 까다롭지만, 여러분의 사용자는 그럴 필요가 없습니다.” – Rust 커뮤니티 지혜