2026-04-11 14:20:02
諏訪子
rust

【Rust】Review of the Rust Language

I(God), am known for not liking the Rust language — mainly because of its insanely crazy community, but also for other reasons: the ABI changes frequently and rapidly, the JavaScript-like mindset of "you must use a third-party dependency for everything", and the extremely slow compilation times.
However, I believe one should at least try using it once and judge it on a technical level, so I decided to give it a try.
As a game developer, I'll write an OpenGL renderer in Rust.
An OpenGL renderer is like a TODO website for game developers.
This review will follow a very similar format to my previous Odin review, but I'll skip the "Hello, World!" part, and this time I'll be working on Windows instead of FreeBSD.

Just to be clear, I am a C and C++ programmer, so please understand that I do have some bias against Rust.
Even so, I intend to keep this article as neutral as possible and only praise or criticize it from a purely technical perspective.

Installation

I ran into problems before I even installed Rust.
I encountered Rust issues even before installing it!
The documentation instructs you to download and run rustup-init.exe.
That's a common method, but when you run it, it behaves like a BAT script.
On Linux and macOS, it's set up to download a remote script with cURL and pipe it directly into a POSIX Shell for execution.
Do you realize how insane this is!?

Rust advertises itself as "the ultimate, perfectly memory-safe language," yet its installation method is literally a security vulnerability.
Everyone who has just started learning cybersecurity is repeatedly warned: "Never blindly trust remote scripts — read every single line before executing them".
You can check the contents first with curl https://sh.rustup.rs | less before running it, but the documentation does not recommend this; it simply tells you to run it.
Even if it's from an official source, it should be manually verified.
This is because there's always the possibility of remote tampering by attackers (like what happened with Linux Mint before).
I checked it myself and, at the time of writing, found nothing suspicious, so I went ahead and ran it.

For those who may read this article in the future, the Rust version I used was 1.94.1.
It was the latest at the time of writing, but given Rust's update speed, it may already be outdated by the time this article is published.

Initial Setup

This isn't my favorite part of any language's project setup, but I can tolerate it since it's usually a one-time thing.

> cd .\dev\stub
> cargo new opengl-render-rs
cargo new opengl-render-rs
    Creating binary (application) `opengl-render-rs` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
> cd .\opengl-render-rs
> ls


    ディレクトリ: C:\Users\suwako\dev\stub\opengl-render-rs


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2026/04/10     17:34                src
-a----        2026/04/10     17:34              8 .gitignore
-a----        2026/04/10     17:34             87 Cargo.toml


> nvim .\Cargo.toml

So far, so good.
However, I ran into a disappointing issue here.
The way dependencies are handled is completely different from C, C++, Go, and Zig.
There's a file called Cargo.toml, which strongly reminds me of "modern" web development.
Another problem is that the Rust official documentation is almost useless when it comes to the daily task of adding dependencies that Rust developers actually do.
When I looked at online guides, some dependencies required writing information inside a scope, while others only needed a string with the version number, with no explanation of which method to use.

[dependencies]
gl = "0.14.0"
glfw = "0.62.0"

> cargo build
    Updating crates.io index
     Locking 125 packages to latest Rust 1.94.1 compatible versions
  Downloaded aligned v0.4.3
  Downloaded avif-serialize v0.8.8
  Downloaded core2 v0.4.0
  Downloaded new_debug_unreachable v1.0.6
  Downloaded zune-inflate v0.2.54
  Downloaded zune-core v0.5.1
  Downloaded zune-jpeg v0.5.15
  Downloaded quote v1.0.45
  Downloaded y4m v0.8.0
  Downloaded simd-adler32 v0.3.9
  Downloaded v_frame v0.3.9
  Downloaded thiserror-impl v2.0.18
  Downloaded crc32fast v1.5.0
  Downloaded memchr v2.8.0
  Downloaded smallvec v1.15.1
  Downloaded bitflags v2.11.0
  Downloaded xml-rs v0.8.28
  Downloaded qoi v0.4.1
  Downloaded zerocopy-derive v0.8.48
  Downloaded tiff v0.11.3
  Downloaded cc v1.2.59
  Downloaded png v0.18.1
  Downloaded moxcms v0.8.1
  Downloaded itertools v0.14.0
  Downloaded rayon v1.11.0
  Downloaded nom v8.0.0
  Downloaded exr v1.74.0
  Downloaded glfw v0.62.0
  Downloaded num-bigint v0.4.6
  Downloaded imgref v1.12.0
  Downloaded zerocopy v0.8.48
  Downloaded syn v2.0.117
  Downloaded image v0.25.10
  Downloaded flate2 v1.1.9
  Downloaded bumpalo v3.20.2
  Downloaded rayon-core v1.13.0
  Downloaded av-scenechange v0.14.1
  Downloaded proc-macro2 v1.0.106
  Downloaded bitstream-io v4.9.0
  Downloaded image-webp v0.2.4
  Downloaded wasm-bindgen-macro-support v0.2.117
  Downloaded wasm-bindgen v0.2.117
  Downloaded unicode-ident v1.0.24
  Downloaded num-traits v0.2.19
  Downloaded half v2.7.1
  Downloaded crossbeam-epoch v0.9.18
  Downloaded bytemuck v1.25.0
  Downloaded rustversion v1.0.22
  Downloaded khronos_api v3.1.0
  Downloaded once_cell v1.21.4
  Downloaded log v0.4.29
  Downloaded getrandom v0.3.4
  Downloaded crossbeam-utils v0.8.21
  Downloaded built v0.8.0
  Downloaded bitflags v1.3.2
  Downloaded either v1.15.0
  Downloaded cmake v0.1.58
  Downloaded num-rational v0.4.2
  Downloaded libc v0.2.184
  Downloaded thiserror v2.0.18
  Downloaded shlex v1.3.0
  Downloaded rgb v0.8.53
  Downloaded pxfm v0.1.28
  Downloaded raw-window-handle v0.6.2
  Downloaded ravif v0.13.0
  Downloaded num-integer v0.1.46
  Downloaded profiling-procmacros v1.0.17
  Downloaded pkg-config v0.3.32
  Downloaded glfw-sys v8.0.0
  Downloaded paste v1.0.15
  Downloaded miniz_oxide v0.8.9
  Downloaded jobserver v0.1.34
  Downloaded gl_generator v0.14.0
  Downloaded gl v0.14.0
  Downloaded fdeflate v0.3.7
  Downloaded color_quant v1.1.0
  Downloaded wasm-bindgen-shared v0.2.117
  Downloaded wasm-bindgen-macro v0.2.117
  Downloaded stable_deref_trait v1.2.1
  Downloaded profiling v1.0.17
  Downloaded winapi v0.3.9
  Downloaded pastey v0.1.1
  Downloaded weezl v0.1.12
  Downloaded simd_helpers v0.1.0
  Downloaded quick-error v2.0.1
  Downloaded loop9 v0.1.5
  Downloaded fax v0.2.6
  Downloaded byteorder-lite v0.1.0
  Downloaded bit_field v0.10.3
  Downloaded num-derive v0.4.2
  Downloaded noop_proc_macro v0.3.0
  Downloaded maybe-rayon v0.1.1
  Downloaded gif v0.14.2
  Downloaded equator-macro v0.4.2
  Downloaded rav1e v0.8.1
  Downloaded equator v0.4.2
  Downloaded lebe v0.5.3
  Downloaded find-msvc-tools v0.1.9
  Downloaded fax_derive v0.2.0
  Downloaded crossbeam-deque v0.8.6
  Downloaded cfg-if v1.0.4
  Downloaded av1-grain v0.2.5
  Downloaded autocfg v1.5.0
  Downloaded as-slice v0.2.1
  Downloaded arrayvec v0.7.6
  Downloaded anyhow v1.0.102
  Downloaded arg_enum_proc_macro v0.3.4
  Downloaded aligned-vec v0.6.4
  Downloaded adler2 v2.0.1
  Downloaded 109 crates (11.1MiB) in 10.33s (largest was `rav1e` at 1.4MiB)
   Compiling proc-macro2 v1.0.106
   Compiling unicode-ident v1.0.24
   Compiling quote v1.0.45
   Compiling autocfg v1.5.0
   Compiling crossbeam-utils v0.8.21
   Compiling cfg-if v1.0.4
   Compiling rayon-core v1.13.0
   Compiling log v0.4.29
   Compiling simd-adler32 v0.3.9
   Compiling memchr v2.8.0
   Compiling anyhow v1.0.102
   Compiling either v1.15.0
   Compiling zerocopy v0.8.48
   Compiling arrayvec v0.7.6
   Compiling stable_deref_trait v1.2.1
   Compiling thiserror v2.0.18
   Compiling crc32fast v1.5.0
   Compiling adler2 v2.0.1
   Compiling miniz_oxide v0.8.9
   Compiling as-slice v0.2.1
   Compiling shlex v1.3.0
   Compiling paste v1.0.15
   Compiling khronos_api v3.1.0
   Compiling built v0.8.0
   Compiling libc v0.2.184
   Compiling av-scenechange v0.14.1
   Compiling num-traits v0.2.19
   Compiling find-msvc-tools v0.1.9
   Compiling nom v8.0.0
   Compiling core2 v0.4.0
   Compiling aligned v0.4.3
   Compiling quick-error v2.0.1
   Compiling rav1e v0.8.1
   Compiling pastey v0.1.1
   Compiling cc v1.2.59
   Compiling y4m v0.8.0
   Compiling flate2 v1.1.9
   Compiling bitstream-io v4.9.0
   Compiling itertools v0.14.0
   Compiling zune-core v0.5.1
   Compiling crossbeam-epoch v0.9.18
   Compiling pkg-config v0.3.32
   Compiling imgref v1.12.0
   Compiling noop_proc_macro v0.3.0
   Compiling new_debug_unreachable v1.0.6
   Compiling weezl v0.1.12
   Compiling xml-rs v0.8.28
   Compiling crossbeam-deque v0.8.6
   Compiling loop9 v0.1.5
   Compiling zune-jpeg v0.5.15
   Compiling avif-serialize v0.8.8
   Compiling syn v2.0.117
   Compiling num-integer v0.1.46
   Compiling cmake v0.1.58
   Compiling simd_helpers v0.1.0
   Compiling zune-inflate v0.2.54
   Compiling fdeflate v0.3.7
   Compiling bytemuck v1.25.0
   Compiling num-bigint v0.4.6
   Compiling gl_generator v0.14.0
   Compiling bit_field v0.10.3
   Compiling smallvec v1.15.1
   Compiling rayon v1.11.0
   Compiling rgb v0.8.53
   Compiling lebe v0.5.3
   Compiling winapi v0.3.9
   Compiling bitflags v2.11.0
   Compiling pxfm v0.1.28
   Compiling color_quant v1.1.0
   Compiling byteorder-lite v0.1.0
   Compiling qoi v0.4.1
   Compiling gif v0.14.2
   Compiling png v0.18.1
   Compiling image-webp v0.2.4
   Compiling glfw-sys v8.0.0
   Compiling raw-window-handle v0.6.2
   Compiling bitflags v1.3.2
   Compiling gl v0.14.0
   Compiling num-rational v0.4.2
   Compiling maybe-rayon v0.1.1
   Compiling equator-macro v0.4.2
   Compiling zerocopy-derive v0.8.48
   Compiling thiserror-impl v2.0.18
   Compiling arg_enum_proc_macro v0.3.4
   Compiling profiling-procmacros v1.0.17
   Compiling num-derive v0.4.2
   Compiling fax_derive v0.2.0
   Compiling moxcms v0.8.1
   Compiling profiling v1.0.17
   Compiling fax v0.2.6
   Compiling equator v0.4.2
   Compiling aligned-vec v0.6.4
   Compiling v_frame v0.3.9
   Compiling av1-grain v0.2.5
   Compiling half v2.7.1
   Compiling tiff v0.11.3
   Compiling exr v1.74.0
   Compiling ravif v0.13.0
   Compiling image v0.25.10
   Compiling glfw v0.62.0
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 53.00s

This resulted in downloading 89 "crates" and compiling 135 of them.
Why on earth are so many needed?
OpenGL should just be OpenGL — at most GLEW or GLAD, and GLFW for the windowing API I'm using.
Yet I ended up downloading a huge amount of junk whose purpose I don't even understand.
"Modern" developers get angry when I say that relying on a massive number of dependencies is extremely dangerous.
Each of these 135 dependencies is a potential attack vector (and in some cases, multiple vectors depending on their complexity).
This criticism is not directed only at Rust developers, but at almost all languages: PHP, JavaScript, Go, Zig, C, C++, C#, Java, etc.

Some may counter with "But Rust is inherently safe", but the unnecessary layers stacked on top can still be exploited in supply-chain attacks.
It doesn't matter what language they are written in.
Just because you're using a "memory-safe" language doesn't mean you can be careless.
If I hadn't been learning Rust for the first time while writing this article, I would have made my own dependencies that only used what gl and glfw actually needed.

Creating the Window

extern crate glfw;
use glfw::{Action, Context, Key};

fn main() {
  use glfw::fail_on_errors;
  let mut glfw = glfw::init(fail_on_errors!()).unwrap();

  let (mut window, events) =
      glfw.create_window(800, 600, "OpenGLレンダー", glfw::WindowMode::Windowed)
      .expect("GLFWウィンドウを作成に失敗。");

  window.make_current();
  window.set_key_polling(true);

  while !window.should_close() {
    window.swap_buffers();

    glfw.poll_events();
    for (_, event) in glfw::flush_messages(&events) {
      println!("{:?}", event);
      match event {
        glfw::WindowEvent::Key(Key::Q, _, Action::Press, _) => {
          window.set_should_close(true)
        },
        _ => {},
      }
    }
  }
}

> cargo build
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.37s
> .\target\debug\opengl-render-rs.exe
Key(Q, 16, Press, (empty))

Window

The code itself isn't bad.
It's surprisingly short, there's no explicit cleanup anywhere, and it feels like a high-level language.
I didn't like how long the create_window line became, so I split it into three lines to fit within my arbitrary 88-character line limit.
As for the code itself, it's not bad at all.

The next step needs to be done inside WSL2.

$ cd /mnt/c/Users/suwako/dev/stub/opengl-render-rs
$ ldd target/debug/opengl-render-rs.exe
        動的実行ファイルではありません
$ file target/debug/opengl-render-rs.exe
target/debug/opengl-render-rs.exe: PE32+ executable for MS Windows 6.00 (console), x86-64, 5 sections

It was really nice that it compiled as a static binary.
I like that the compiler does static linking by default.
Let's move on to some real rendering.
I'll do almost the same thing I did in the Odin review, but this time using OpenGL 4.6 instead of 3.3.

Let's check if OpenGL works.

extern crate glfw;
extern crate gl;
use glfw::{Action, Context, Key};
use gl::types::*;

const VERTEX_SRC: &str = r#"
#version 460 core

layout (location = 0) in vec3 aPos;

void main() {
  gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
"#;

const FRAG_SRC: &str = r#"
#version 460 core

out vec4 FragColor;

void main() {
  FragColor = vec4(1.f, .5f, .2f, 1.f);
}
"#;

fn main() {
  use glfw::fail_on_errors;
  let mut glfw = glfw::init(glfw::fail_on_errors!()).unwrap();

  glfw.window_hint(glfw::WindowHint::ContextVersionMajor(4));
  glfw.window_hint(glfw::WindowHint::ContextVersionMinor(6));
  glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));

  let (mut window, events) =
      glfw.create_window(800, 600, "OpenGLレンダー", glfw::WindowMode::Windowed)
      .expect("GLFWウィンドウを作成に失敗。");

  window.make_current();

  gl::load_with(|s| {
    window.get_proc_address(s)
      .map_or(std::ptr::null(), |f| f as *const _)
  });

  window.set_key_polling(true);

  window.set_framebuffer_size_callback(|_wnd, w, h| {
    unsafe { gl::Viewport(0, 0, w, h); }
  });

  while !window.should_close() {
    unsafe {
      gl::ClearColor(1.0, 0.2, 0.9, 1.0);
      gl::Clear(gl::COLOR_BUFFER_BIT);
    }

    window.swap_buffers();

    glfw.poll_events();
    for (_, event) in glfw::flush_messages(&events) {
      match event {
        glfw::WindowEvent::Key(Key::Q, _, Action::Press, _) => {
          window.set_should_close(true)
        },
        _ => {},
      }
    }
  }
}

> cargo build
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
warning: unused import: `gl::types::*`
 --> src\main.rs:4:5
  |
4 | use gl::types::*;
  |     ^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default

warning: constant `VERTEX_SRC` is never used
 --> src\main.rs:6:7
  |
6 | const VERTEX_SRC: &str = r#"
  |       ^^^^^^^^^^
  |
  = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default

warning: constant `FRAG_SRC` is never used
  --> src\main.rs:16:7
   |
16 | const FRAG_SRC: &str = r#"
   |       ^^^^^^^^

warning: `opengl-render-rs` (bin "opengl-render-rs") generated 3 warnings (run `cargo fix --bin "opengl-render-rs" -p opengl-render-rs` to apply 1 suggestion)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.45s
> .\target\debug\opengl-render-rs.exe

Color

It worked!
However, I haven't done anything with shaders yet, so I'll implement that next.

Wait, before that, let me point out one more thing:

extern crate glfw;
extern crate gl;
use glfw::{Action, Context, Key};
use gl::types::*;
use std::ffi::CString;
use std::ptr;

const VERTEX_SRC: &str = r#"
#version 460 core

layout (location = 0) in vec3 aPos;

void main() {
  gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
"#;

const FRAG_SRC: &str = r#"
#version 460 core

out vec4 FragColor;

void main() {
  FragColor = vec4(1.f, .5f, .2f, 1.f);
}
"#;

fn main() {
  use glfw::fail_on_errors;
  let mut glfw = glfw::init(glfw::fail_on_errors!()).unwrap();

  glfw.window_hint(glfw::WindowHint::ContextVersionMajor(4));
  glfw.window_hint(glfw::WindowHint::ContextVersionMinor(6));
  glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));

  let (mut window, events) =
    glfw.create_window(800, 600, "OpenGLレンダー", glfw::WindowMode::Windowed)
    .expect("GLFWウィンドウを作成に失敗。");

  window.make_current();

  gl::load_with(|s| {
    window.get_proc_address(s)
      .map_or(std::ptr::null(), |f| f as *const _)
  });

  window.set_key_polling(true);

  window.set_framebuffer_size_callback(|_wnd, w, h| {
    unsafe { gl::Viewport(0, 0, w, h); }
  });

  unsafe {
    let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
    let cVtx = CString::new(VERTEX_SRC.as_bytes())
      .expect("頂点シェーダー向けCStringに変換に失敗。");
    gl::ShaderSource(vertexShader, 1, &cVtx.as_ptr(), ptr::null());
    gl::CompileShader(vertexShader);

    let mut success = gl::FALSE as GLint;
    gl::GetShaderiv(vertexShader, gl::COMPILE_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(vertexShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(vertexShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("頂点シェーダーコンパイルに失敗。\n{}", log);
    }

    let fragShader = gl::CreateShader(gl::FRAGMENT_SHADER);
    let cFrag = CString::new(FRAG_SRC.as_bytes())
      .expect("フラグメントシェーダー向けCStringに変換に失敗。");
    gl::ShaderSource(fragShader, 1, &cFrag.as_ptr(), ptr::null());
    gl::CompileShader(fragShader);

    gl::GetShaderiv(fragShader, gl::COMPILE_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("フラグメントシェーダーコンパイルに失敗。\n{}", log);
    }

    let shaderProgram = gl::CreateProgram();
    gl::AttachShader(shaderProgram, vertexShader);
    gl::AttachShader(shaderProgram, fragShader);
    gl::LinkProgram(shaderProgram);

    gl::GetProgramiv(shaderProgram, gl::LINK_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("プログラムを受け取るに失敗。\n{}", log);
    }

    gl::DeleteShader(vertexShader);
    gl::DeleteShader(fragShader);
  }

  while !window.should_close() {
    unsafe {
      gl::ClearColor(.6, .1, 0.6, 1.0);
      gl::Clear(gl::COLOR_BUFFER_BIT);
    }

    window.swap_buffers();

    glfw.poll_events();
    for (_, event) in glfw::flush_messages(&events) {
      match event {
        glfw::WindowEvent::Key(Key::Q, _, Action::Press, _) => {
          window.set_should_close(true)
        },
        _ => {},
      }
    }
  }
}

cargo build
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
error: float literals must have an integer part
   --> src\main.rs:111:22
    |
111 |       gl::ClearColor(.6, .1, 0.6, 1.0);
    |                      ^^
    |
help: must have an integer part
    |
111 |       gl::ClearColor(0.6, .1, 0.6, 1.0);
    |                      +

error: float literals must have an integer part
   --> src\main.rs:111:26
    |
111 |       gl::ClearColor(.6, .1, 0.6, 1.0);
    |                          ^^
    |
help: must have an integer part
    |
111 |       gl::ClearColor(.6, 0.1, 0.6, 1.0);
    |                          +

error: could not compile `opengl-render-rs` (bin "opengl-render-rs") due to 2 previous errors

It seems Rust doesn't allow omitting the zero.
In C, C++, and the Odin I looked at earlier, it worked fine...
But that's not the real issue.
The real issue is that almost all of the OpenGL-related code has to be placed inside unsafe blocks.

The compiler output when I first compiled it looked like this:

error[E0133]: call to unsafe function `CreateShader` is unsafe and requires unsafe block
  --> src\main.rs:53:22
   |
53 |   let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `ShaderSource` is unsafe and requires unsafe block
  --> src\main.rs:56:3
   |
56 |   gl::ShaderSource(vertexShader, 1, &cVtx.as_ptr(), ptr::null());
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `CompileShader` is unsafe and requires unsafe block
  --> src\main.rs:57:3
   |
57 |   gl::CompileShader(vertexShader);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderiv` is unsafe and requires unsafe block
  --> src\main.rs:60:3
   |
60 |   gl::GetShaderiv(vertexShader, gl::COMPILE_STATUS, &mut success);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderiv` is unsafe and requires unsafe block
  --> src\main.rs:63:5
   |
63 |     gl::GetShaderiv(vertexShader, gl::INFO_LOG_LENGTH, &mut len);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderInfoLog` is unsafe and requires unsafe block
  --> src\main.rs:65:5
   |
65 | /     gl::GetShaderInfoLog(vertexShader, len, ptr::null_mut(),
66 | |       infoLog.as_mut_ptr() as *mut GLchar);
   | |__________________________________________^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `CreateShader` is unsafe and requires unsafe block
  --> src\main.rs:71:20
   |
71 |   let fragShader = gl::CreateShader(gl::FRAGMENT_SHADER);
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `ShaderSource` is unsafe and requires unsafe block
  --> src\main.rs:74:3
   |
74 |   gl::ShaderSource(fragShader, 1, &cFrag.as_ptr(), ptr::null());
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `CompileShader` is unsafe and requires unsafe block
  --> src\main.rs:75:3
   |
75 |   gl::CompileShader(fragShader);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderiv` is unsafe and requires unsafe block
  --> src\main.rs:77:3
   |
77 |   gl::GetShaderiv(fragShader, gl::COMPILE_STATUS, &mut success);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderiv` is unsafe and requires unsafe block
  --> src\main.rs:80:5
   |
80 |     gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderInfoLog` is unsafe and requires unsafe block
  --> src\main.rs:82:5
   |
82 | /     gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
83 | |       infoLog.as_mut_ptr() as *mut GLchar);
   | |__________________________________________^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `CreateProgram` is unsafe and requires unsafe block
  --> src\main.rs:88:23
   |
88 |   let shaderProgram = gl::CreateProgram();
   |                       ^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `AttachShader` is unsafe and requires unsafe block
  --> src\main.rs:89:3
   |
89 |   gl::AttachShader(shaderProgram, vertexShader);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `AttachShader` is unsafe and requires unsafe block
  --> src\main.rs:90:3
   |
90 |   gl::AttachShader(shaderProgram, fragShader);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `LinkProgram` is unsafe and requires unsafe block
  --> src\main.rs:91:3
   |
91 |   gl::LinkProgram(shaderProgram);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetProgramiv` is unsafe and requires unsafe block
  --> src\main.rs:93:3
   |
93 |   gl::GetProgramiv(shaderProgram, gl::LINK_STATUS, &mut success);
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderiv` is unsafe and requires unsafe block
  --> src\main.rs:96:5
   |
96 |     gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `GetShaderInfoLog` is unsafe and requires unsafe block
  --> src\main.rs:98:5
   |
98 | /     gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
99 | |       infoLog.as_mut_ptr() as *mut GLchar);
   | |__________________________________________^ call to unsafe function
   |
   = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `DeleteShader` is unsafe and requires unsafe block
   --> src\main.rs:104:3
    |
104 |   gl::DeleteShader(vertexShader);
    |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
    |
    = note: consult the function's documentation for information on how to avoid undefined behavior

error[E0133]: call to unsafe function `DeleteShader` is unsafe and requires unsafe block
   --> src\main.rs:105:3
    |
105 |   gl::DeleteShader(fragShader);
    |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
    |
    = note: consult the function's documentation for information on how to avoid undefined behavior

For more information about this error, try `rustc --explain E0133`.
error: could not compile `opengl-render-rs` (bin "opengl-render-rs") due to 23 previous errors

I understand that it's a driver that needs to communicate with the GPU.
However, if almost all graphics-related code ends up being "unsafe", then what's the point of using Rust for game development?

Another frustration is the Rust compiler's grammar-police behavior.

> cargo build
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
warning: variable `vertexShader` should have a snake case name
  --> src\main.rs:54:9
   |
54 |     let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
   |         ^^^^^^^^^^^^ help: convert the identifier to snake case: `vertex_shader`
   |
   = note: `#[warn(non_snake_case)]` (part of `#[warn(nonstandard_style)]`) on by default

warning: variable `cVtx` should have a snake case name
  --> src\main.rs:55:9
   |
55 |     let cVtx = CString::new(VERTEX_SRC.as_bytes())
   |         ^^^^ help: convert the identifier to snake case: `c_vtx`

warning: variable `infoLog` should have a snake case name
  --> src\main.rs:65:15
   |
65 |       let mut infoLog = vec![0u8; len as usize];
   |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: variable `fragShader` should have a snake case name
  --> src\main.rs:72:9
   |
72 |     let fragShader = gl::CreateShader(gl::FRAGMENT_SHADER);
   |         ^^^^^^^^^^ help: convert the identifier to snake case: `frag_shader`

warning: variable `cFrag` should have a snake case name
  --> src\main.rs:73:9
   |
73 |     let cFrag = CString::new(FRAG_SRC.as_bytes())
   |         ^^^^^ help: convert the identifier to snake case: `c_frag`

warning: variable `infoLog` should have a snake case name
  --> src\main.rs:82:15
   |
82 |       let mut infoLog = vec![0u8; len as usize];
   |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: variable `shaderProgram` should have a snake case name
  --> src\main.rs:89:9
   |
89 |     let shaderProgram = gl::CreateProgram();
   |         ^^^^^^^^^^^^^ help: convert the identifier to snake case: `shader_program`

warning: variable `infoLog` should have a snake case name
  --> src\main.rs:98:15
   |
98 |       let mut infoLog = vec![0u8; len as usize];
   |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: `opengl-render-rs` (bin "opengl-render-rs") generated 8 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.87s

At least let me decide how to declare variables like an adult!
Final code:

extern crate glfw;
extern crate gl;
use glfw::{Action, Context, Key};
use gl::types::*;
use std::ffi::CString;
use std::ptr;

const VERTEX_SRC: &str = r#"
#version 460 core

layout (location = 0) in vec3 aPos;

void main() {
  gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
"#;

const FRAG_SRC: &str = r#"
#version 460 core

out vec4 FragColor;

void main() {
  FragColor = vec4(1.f, .5f, .2f, 1.f);
}
"#;

fn main() {
  use glfw::fail_on_errors;
  let mut glfw = glfw::init(glfw::fail_on_errors!()).unwrap();

  glfw.window_hint(glfw::WindowHint::ContextVersionMajor(4));
  glfw.window_hint(glfw::WindowHint::ContextVersionMinor(6));
  glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));

  let (mut window, events) =
    glfw.create_window(800, 600, "OpenGLレンダー", glfw::WindowMode::Windowed)
    .expect("GLFWウィンドウを作成に失敗。");

  window.make_current();

  gl::load_with(|s| {
    window.get_proc_address(s)
      .map_or(std::ptr::null(), |f| f as *const _)
  });

  window.set_key_polling(true);

  window.set_framebuffer_size_callback(|_wnd, w, h| {
    unsafe { gl::Viewport(0, 0, w, h); }
  });

  let mut shaderProgram = 0;

  unsafe {
    let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
    let cVtx = CString::new(VERTEX_SRC.as_bytes())
      .expect("頂点シェーダー向けCStringに変換に失敗。");
    gl::ShaderSource(vertexShader, 1, &cVtx.as_ptr(), ptr::null());
    gl::CompileShader(vertexShader);

    let mut success = gl::FALSE as GLint;
    gl::GetShaderiv(vertexShader, gl::COMPILE_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(vertexShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(vertexShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("頂点シェーダーコンパイルに失敗。\n{}", log);
    }

    let fragShader = gl::CreateShader(gl::FRAGMENT_SHADER);
    let cFrag = CString::new(FRAG_SRC.as_bytes())
      .expect("フラグメントシェーダー向けCStringに変換に失敗。");
    gl::ShaderSource(fragShader, 1, &cFrag.as_ptr(), ptr::null());
    gl::CompileShader(fragShader);

    gl::GetShaderiv(fragShader, gl::COMPILE_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("フラグメントシェーダーコンパイルに失敗。\n{}", log);
    }

    shaderProgram = gl::CreateProgram();
    gl::AttachShader(shaderProgram, vertexShader);
    gl::AttachShader(shaderProgram, fragShader);
    gl::LinkProgram(shaderProgram);

    gl::GetProgramiv(shaderProgram, gl::LINK_STATUS, &mut success);
    if success == gl::FALSE as GLint {
      let mut len = 0;
      gl::GetShaderiv(fragShader, gl::INFO_LOG_LENGTH, &mut len);
      let mut infoLog = vec![0u8; len as usize];
      gl::GetShaderInfoLog(fragShader, len, ptr::null_mut(),
        infoLog.as_mut_ptr() as *mut GLchar);
      let log = String::from_utf8_lossy(&infoLog);
      panic!("プログラムを受け取るに失敗。\n{}", log);
    }

    gl::DeleteShader(vertexShader);
    gl::DeleteShader(fragShader);
  }

  const VERTICES: [f32; 12] = [
     0.5,  0.5, 0.0,
     0.5, -0.5, 0.0,
    -0.5, -0.5, 0.0,
    -0.5,  0.5, 0.0,
  ];

  const INDICES: [u32; 6] = [
    0, 1, 3,
    1, 2, 3,
  ];

  let mut VAO = 0;
  let mut VBO = 0;
  let mut EBO = 0;

  unsafe {
    gl::GenVertexArrays(1, &mut VAO);
    gl::GenBuffers(1, &mut VBO);
    gl::GenBuffers(1, &mut EBO);

    gl::BindVertexArray(VAO);

    gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
    gl::BufferData(gl::ARRAY_BUFFER, std::mem::size_of_val(&VERTICES) as isize,
      VERTICES.as_ptr() as *const _, gl::STATIC_DRAW);

    gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, EBO);
    gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, std::mem::size_of_val(&INDICES) as isize,
      INDICES.as_ptr() as *const _, gl::STATIC_DRAW);

    gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE,
      3 * std::mem::size_of::<f32>() as GLint, std::ptr::null());
    gl::EnableVertexAttribArray(0);

    gl::BindBuffer(gl::ARRAY_BUFFER, 0);
    gl::BindVertexArray(0);
  }

  while !window.should_close() {
    unsafe {
      gl::ClearColor(0.6, 0.1, 0.6, 1.0);
      gl::Clear(gl::COLOR_BUFFER_BIT);
      gl::UseProgram(shaderProgram);
      gl::BindVertexArray(VAO);
      gl::DrawElements(gl::TRIANGLES, INDICES.len() as GLint, gl::UNSIGNED_INT,
          std::ptr::null());
    }

    window.swap_buffers();

    glfw.poll_events();
    for (_, event) in glfw::flush_messages(&events) {
      match event {
        glfw::WindowEvent::Key(Key::Q, _, Action::Press, _) => {
          window.set_should_close(true)
        },
        _ => {},
      }
    }
  }
}

The compiler spat out a lot of warnings, but since they were just opinions, I didn't fix them.

> cargo build
   Compiling opengl-render-rs v0.1.0 (C:\Users\suwako\dev\stub\opengl-render-rs)
warning: value assigned to `shaderProgram` is never read
  --> src\main.rs:53:27
   |
53 |   let mut shaderProgram = 0;
   |                           ^
   |
   = help: maybe it is overwritten before being read?
   = note: `#[warn(unused_assignments)]` (part of `#[warn(unused)]`) on by default

warning: variable `shaderProgram` should have a snake case name
  --> src\main.rs:53:11
   |
53 |   let mut shaderProgram = 0;
   |           ^^^^^^^^^^^^^ help: convert the identifier to snake case: `shader_program`
   |
   = note: `#[warn(non_snake_case)]` (part of `#[warn(nonstandard_style)]`) on by default

warning: variable `vertexShader` should have a snake case name
  --> src\main.rs:56:9
   |
56 |     let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
   |         ^^^^^^^^^^^^ help: convert the identifier to snake case: `vertex_shader`

warning: variable `cVtx` should have a snake case name
  --> src\main.rs:57:9
   |
57 |     let cVtx = CString::new(VERTEX_SRC.as_bytes())
   |         ^^^^ help: convert the identifier to snake case: `c_vtx`

warning: variable `infoLog` should have a snake case name
  --> src\main.rs:67:15
   |
67 |       let mut infoLog = vec![0u8; len as usize];
   |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: variable `fragShader` should have a snake case name
  --> src\main.rs:74:9
   |
74 |     let fragShader = gl::CreateShader(gl::FRAGMENT_SHADER);
   |         ^^^^^^^^^^ help: convert the identifier to snake case: `frag_shader`

warning: variable `cFrag` should have a snake case name
  --> src\main.rs:75:9
   |
75 |     let cFrag = CString::new(FRAG_SRC.as_bytes())
   |         ^^^^^ help: convert the identifier to snake case: `c_frag`

warning: variable `infoLog` should have a snake case name
  --> src\main.rs:84:15
   |
84 |       let mut infoLog = vec![0u8; len as usize];
   |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: variable `infoLog` should have a snake case name
   --> src\main.rs:100:15
    |
100 |       let mut infoLog = vec![0u8; len as usize];
    |               ^^^^^^^ help: convert the identifier to snake case: `info_log`

warning: variable `VAO` should have a snake case name
   --> src\main.rs:123:11
    |
123 |   let mut VAO = 0;
    |           ^^^ help: convert the identifier to snake case: `vao`

warning: variable `VBO` should have a snake case name
   --> src\main.rs:124:11
    |
124 |   let mut VBO = 0;
    |           ^^^ help: convert the identifier to snake case: `vbo`

warning: variable `EBO` should have a snake case name
   --> src\main.rs:125:11
    |
125 |   let mut EBO = 0;
    |           ^^^ help: convert the identifier to snake case: `ebo`

warning: `opengl-render-rs` (bin "opengl-render-rs") generated 12 warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.78s
> .\target\debug\opengl-render-rs.exe

Render

Overall Impressions

To be honest, an OpenGL renderer is pretty much the same in any language, so it's not a particularly complex project.
Still, here are my impressions after trying Rust for the first time.

Good points:

  • Surprisingly fast compilation times once dependencies are installed
  • Static linking by default (at least on Windows)
  • The compiler clearly tells you what the problem is and how to fix it
  • No explicit cleanup anywhere — it feels like a high-level language
  • It's refreshing that error messages can be defined inline

Bad points:

  • The compiler installation method is extremely dangerous
  • A massive dependency hell on par with Node.js or Python
  • Compilation takes forever if dependencies aren't already installed
  • In graphics programming, there's more "unsafe" code than "safe" code
  • Tons of warnings for trivial things (like variable naming conventions)
  • Lines tend to become very verbose, often requiring splitting into multiple lines to stay within 88 characters

Overall, using Rust didn't give me much of a feeling that I was "writing safer code".
It's good that the compiler suggests solutions to many problems, but many of the warnings feel off-target.
And as mentioned earlier, since OpenGL is not a high-level library but a driver that talks directly to the GPU, it's natural that there would be many unsafe blocks.

One more thing I noticed: in many other languages, variables are mutable by default and you add const to make them constant, whereas in Rust, variables are constant by default and you add mut to make them mutable.
This is neither good nor bad — I just noticed it.

To me, Rust feels like the JavaScript of systems programming.
That also shows in the louder parts of its community.

Just because I made an OpenGL renderer in Rust doesn't mean I'm going to get plastic surgery, start hormone therapy, or wave an LGBT(mental disorder) flag.
I am still a proud C and C++ developer.
Occasionally trying the other side helps me understand why other languages exist and why other people like them.

Next time, I'll try Zig again.
It'll feel a bit strange, because I've already used Zig before.
However, that was with Zig 0.11.0, and the release of Zig 0.16.0 is getting quite close now.
I'll probably use that version.
By the time I write the article, it might still be in development.

以上