はじめに
今までC++でコードを書いてきましたが、「Rustも触ってみたい!」ということで、今回はRustのwinitを使ってウインドウを作成していきたいと思います。
この記事では、C++経験者の視点から「Rustではこう書くのか」という点も交えながら解説していきます。
Cargo.tomlにwinitを追加
Rustでは、使用するライブラリをCargo.tomlに記述します。
C++でいうライブラリの追加に近いイメージです。
[package]
name = "RustEngine"
version = "0.1.0"
edition = "2024"
[dependencies]
winit = "0.30"
use宣言
まずは必要な型や関数をuseで読み込みます。
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
event::WindowEvent,
event_loop::{ActiveEventLoop, EventLoop},
window::{Window, WindowId},
};
useは、C++のusing宣言に近い機能です。
モジュール内にある型や関数を現在のスコープで利用できるようにしています。
アプリケーションクラス(構造体)の定義
RustにはC++のようなクラスという構文はありません。
その代わりに、
structimpltrait
を組み合わせることで、クラスに近いことができます。
まずはアプリケーションを表す構造体を作成します。
#[derive(Default)]
struct App {
window: Option<Window>,
}
struct AppはC++でいうstructに近いものです。
その中に
window: Option<Window>,
というメンバ変数を持っています。
Option<T>は、C++のstd::optional<T>とほぼ同じで、
-
Some(T)・・・値がある -
None・・・値がない
のどちらかを保持できます。
今回はまだウインドウを作成していないため、最初はNoneになります。
#[derive(Default)]とは?
#[derive(Default)]は、Defaultトレイトを自動実装するための属性です。
例えば、
let app = App::default();
と書けるようになります。
実際には、以下のようなコードが自動生成されるイメージです。
impl Default for App {
fn default() -> Self {
Self {
window: None,
}
}
}
つまり、
let app = App::default();
を実行すると、
App {
window: None,
}
という状態で生成されます。
ApplicationHandlerの実装
続いて、ApplicationHandlerトレイトを実装します。
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attrs = Window::default_attributes()
.with_title("RustEngine")
.with_inner_size(PhysicalSize::new(1280, 720));
self.window = Some(event_loop.create_window(attrs).unwrap());
}
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
_ => {}
}
}
}
impl ApplicationHandler for App
は、App型にApplicationHandlerトレイトを実装しているという意味です。
C++でいうインターフェースを実装するようなイメージが一番近いと思います。
resumed()
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attrs = Window::default_attributes()
.with_title("RustEngine")
.with_inner_size(PhysicalSize::new(1280, 720));
self.window = Some(event_loop.create_window(attrs).unwrap());
}
ここでウインドウを作成しています。
まず、
Window::default_attributes()
でウインドウの設定を作成し、
event_loop.create_window(attrs)
へ渡して実際にウインドウを生成しています。
生成されたWindowは
Some(...)
としてwindowへ保存しています。
なお、winit 0.30ではウインドウはresumed()内で生成するのが基本となっています。
window_event()
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
_window_id: WindowId,
event: WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
_ => {}
}
}
こちらはウインドウに関するイベント処理です。
Rustではswitch文の代わりにmatchを使います。
今回は、
WindowEvent::CloseRequested
つまり、ウインドウの閉じるボタンが押されたときだけ処理を書いています。
event_loop.exit();
を呼ぶことでイベントループを終了し、アプリケーションも終了します。
アプリケーションの実行
最後にmain関数です。
fn main() {
let event_loop = EventLoop::new().unwrap();
let mut app = App::default();
event_loop.run_app(&mut app).unwrap();
}
ここで少し流れを整理してみます。
まず、
let mut app = App::default();
を実行した時点では、
App
└── window = None
という状態です。
その後、
event_loop.run_app(&mut app)
を呼ぶことでイベントループが開始されます。
イベントループ開始後、winitが内部でresumed()を呼び出します。
すると、
self.window = Some(event_loop.create_window(attrs).unwrap());
が実行され、
App
└── window = Some(Window)
となり、実際のウインドウが生成されます。
処理の流れを図にすると次のようになります。
App::default()
│
▼
window = None
│
▼
run_app()
│
▼
resumed() が呼ばれる
│
▼
create_window()
│
▼
window = Some(Window)
│
▼
イベントループ開始
おわりに
今回はRustのwinitを使って、ウインドウを作成してみました。
C++を書いてきた身としては、最初は
- クラスがない
Optiontraitimpl
など、戸惑うことがたくさんありました。
しかし、実際に触ってみると「Rustではこういう考え方なんだな」と少しずつ理解できるようになってきました。
まだまだ勉強することは多いですが、Rustの所有権やトレイトなどをしっかり理解できれば、とても強力な言語だと感じています。