Skilore

システム言語比較(C, C++, Rust, Go, Zig)

システム言語は「ハードウェアに近い制御」と「高いパフォーマンス」を提供する。OS、ドライバ、ゲームエンジン、インフラツールの基盤。

82 分で読めます40,920 文字

システム言語比較(C, C++, Rust, Go, Zig)

システム言語は「ハードウェアに近い制御」と「高いパフォーマンス」を提供する。OS、ドライバ、ゲームエンジン、インフラツールの基盤。

この章で学ぶこと

  • 主要システム言語の特徴と適用領域を把握する
  • メモリ管理戦略の違いを理解する
  • 安全性とパフォーマンスのトレードオフを判断できる
  • 各言語のビルドシステムとツールチェーンを理解する
  • プロジェクト要件に応じた言語選択ができる
  • 各言語のエラーハンドリング戦略を比較できる

前提知識

このガイドを読む前に、以下の知識があると理解が深まります:


1. 比較表

CC++RustGoZig
登場年19721985201520122016
設計者D.RitchieStroustrupHoare+Pike+A.Kelley
MozillaGoogle
メモリ管理手動手動所有権GC手動
mallocRAIIBorrow並行GCalloc
freesmartchecker低遅延comptime
安全性低い中程度高い高い中程度
UB多UB有UB無(safe)メモリ安全UB有
パフォーマンス最速最速最速高速最速
GC pause
コンパイル速度速い遅い遅い非常速速い
ヘッダ借用検査増分
学習コスト中程度高い高い低い中程度
膨大仕様借用25KW
抽象化最低限豊富豊富最低限最低限
レベル関数テンプレトレイトインタフェcomptime
OOPジェネリース
エラー処理戻り値例外Resulterrorerror
errnoRAIIOptionmultiunion
並行処理pthreadthreadSend/goroutineasync
forkasyncSyncchannelevented
主な用途OSゲームインフラクラウド組み込み
組み込みブラウザCLIツールシステム
カーネルDBWasmマイクロゲーム
標準ライブラリ最小限大きい中程度充実最小限
libcSTLstdnet等std
ビルドシステムMakeCMakeCargogo buildzig build
MesonBazel

2. メモリ管理モデルの詳細比較

2.1 C — 手動メモリ管理

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
// C: 手動メモリ管理 — malloc/free の対で管理
typedef struct {
    char* name;
    int age;
    char** tags;
    int tag_count;
} User;
 
User* user_create(const char* name, int age) {
    User* user = (User*)malloc(sizeof(User));
    if (!user) return NULL;  // メモリ確保失敗
 
    user->name = strdup(name);  // 文字列のコピーを確保
    if (!user->name) {
        free(user);
        return NULL;
    }
 
    user->age = age;
    user->tags = NULL;
    user->tag_count = 0;
    return user;
}
 
int user_add_tag(User* user, const char* tag) {
    // realloc で配列を拡張
    char** new_tags = (char**)realloc(
        user->tags,
        sizeof(char*) * (user->tag_count + 1)
    );
    if (!new_tags) return -1;  // メモリ確保失敗
 
    user->tags = new_tags;
    user->tags[user->tag_count] = strdup(tag);
    if (!user->tags[user->tag_count]) return -1;
 
    user->tag_count++;
    return 0;
}
 
void user_destroy(User* user) {
    if (!user) return;
 
    free(user->name);
    for (int i = 0; i < user->tag_count; i++) {
        free(user->tags[i]);
    }
    free(user->tags);
    free(user);
}
 
// 使用例
void example(void) {
    User* alice = user_create("Alice", 30);
    if (!alice) {
        fprintf(stderr, "メモリ確保失敗\n");
        return;
    }
 
    user_add_tag(alice, "admin");
    user_add_tag(alice, "developer");
 
    printf("Name: %s, Age: %d\n", alice->name, alice->age);
 
    user_destroy(alice);  // 必ず解放する(忘れるとメモリリーク)
    // alice = NULL;      // ダングリングポインタ防止(推奨)
}
 
// 典型的なCのバグ: Use After Free
void dangerous_example(void) {
    char* name = strdup("Alice");
    free(name);
    // printf("%s\n", name);  // 未定義動作! 解放後のメモリにアクセス
}
 
// 典型的なCのバグ: バッファオーバーフロー
void buffer_overflow(void) {
    char buf[10];
    // strcpy(buf, "This is a very long string");  // 危険!
    strncpy(buf, "This is a very long string", sizeof(buf) - 1);  // 安全
    buf[sizeof(buf) - 1] = '\0';
}
 
// 典型的なCのバグ: ダブルフリー
void double_free_example(void) {
    char* ptr = malloc(100);
    free(ptr);
    // free(ptr);  // 未定義動作! 二重解放
}

2.2 C++ — RAII とスマートポインタ

#include <string>
#include <memory>
#include <vector>
#include <iostream>
#include <optional>
 
// C++: RAII (Resource Acquisition Is Initialization)
// コンストラクタで獲得、デストラクタで解放
 
class User {
public:
    User(std::string name, int age)
        : name_(std::move(name)), age_(age) {}
 
    // デストラクタ — スコープを抜けると自動呼び出し
    ~User() {
        std::cout << "User " << name_ << " destroyed" << std::endl;
    }
 
    void add_tag(std::string tag) {
        tags_.push_back(std::move(tag));
    }
 
    const std::string& name() const { return name_; }
    int age() const { return age_; }
    const std::vector<std::string>& tags() const { return tags_; }
 
private:
    std::string name_;      // std::string が内部メモリを管理
    int age_;
    std::vector<std::string> tags_;  // vector が配列メモリを管理
};
 
// スマートポインタの使い分け
void smart_pointer_example() {
    // unique_ptr: 唯一の所有権(最も一般的)
    auto alice = std::make_unique<User>("Alice", 30);
    alice->add_tag("admin");
 
    // 所有権の移動(ムーブ)
    auto owner = std::move(alice);
    // alice はもう使えない(nullptr)
 
    // shared_ptr: 共有所有権(参照カウント)
    auto bob = std::make_shared<User>("Bob", 25);
    {
        auto bob_ref = bob;  // 参照カウント +1
        std::cout << "ref count: " << bob.use_count() << std::endl;  // 2
    }
    // bob_ref がスコープを抜けて参照カウント -1
    std::cout << "ref count: " << bob.use_count() << std::endl;  // 1
 
    // weak_ptr: 循環参照の防止
    std::weak_ptr<User> weak = bob;
    if (auto locked = weak.lock()) {
        std::cout << "User still alive: " << locked->name() << std::endl;
    }
}
 
// ムーブセマンティクス(C++11以降)
class LargeBuffer {
    std::vector<uint8_t> data_;
 
public:
    explicit LargeBuffer(size_t size) : data_(size, 0) {}
 
    // ムーブコンストラクタ — データの所有権を移動(コピーなし)
    LargeBuffer(LargeBuffer&& other) noexcept
        : data_(std::move(other.data_)) {}
 
    // ムーブ代入演算子
    LargeBuffer& operator=(LargeBuffer&& other) noexcept {
        data_ = std::move(other.data_);
        return *this;
    }
 
    // コピーを禁止(大きいデータの意図しないコピーを防ぐ)
    LargeBuffer(const LargeBuffer&) = delete;
    LargeBuffer& operator=(const LargeBuffer&) = delete;
 
    size_t size() const { return data_.size(); }
};
 
// std::optional で null を型安全に扱う
std::optional<User> find_user(const std::string& name) {
    if (name == "Alice") {
        return User("Alice", 30);
    }
    return std::nullopt;
}
 
void optional_example() {
    auto user = find_user("Alice");
    if (user.has_value()) {
        std::cout << user->name() << std::endl;
    }
 
    // value_or でデフォルト値
    auto name = find_user("Bob")
        .transform( { return u.name(); })
        .value_or("Unknown");
}
 
// コンセプト(C++20)— テンプレートの型制約
template<typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};
 
template<Printable T>
void print(const T& value) {
    std::cout << value << std::endl;
}
 
// Ranges(C++20)— 関数型スタイルのデータ処理
#include <ranges>
#include <algorithm>
 
void ranges_example() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
    auto result = numbers
        | std::views::filter( { return n % 2 == 0; })
        | std::views::transform( { return n * n; })
        | std::views::take(3);
 
    for (int n : result) {
        std::cout << n << " ";  // 4 16 36
    }
}

2.3 Rust — 所有権と借用チェッカー

use std::collections::HashMap;
 
// Rust: 所有権システム — コンパイル時にメモリ安全性を保証
// 3つのルール:
// 1. 各値は1つの所有者を持つ
// 2. 所有者がスコープを離れると値は破棄される
// 3. &T(不変借用)は複数可、&mut T(可変借用)は1つだけ
 
struct User {
    name: String,
    age: u32,
    tags: Vec<String>,
}
 
impl User {
    fn new(name: impl Into<String>, age: u32) -> Self {
        User {
            name: name.into(),
            age,
            tags: Vec::new(),
        }
    }
 
    fn add_tag(&mut self, tag: impl Into<String>) {
        self.tags.push(tag.into());
    }
 
    // &self: 不変借用(読み取りのみ)
    fn display(&self) -> String {
        format!("{} (age: {}, tags: {:?})", self.name, self.age, self.tags)
    }
 
    // self: 所有権を消費(呼び出し後は使えない)
    fn into_name(self) -> String {
        self.name  // 所有権が移動
    }
}
 
fn ownership_example() {
    let mut alice = User::new("Alice", 30);
    alice.add_tag("admin");
    alice.add_tag("developer");
 
    // 不変借用(同時に複数可能)
    let display1 = alice.display();
    let display2 = alice.display();
    println!("{}", display1);
    println!("{}", display2);
 
    // 所有権の移動
    let name = alice.into_name();
    println!("Name: {}", name);
    // println!("{}", alice.display());  // コンパイルエラー! aliceはもう使えない
}
 
// ライフタイム — 参照の有効期間をコンパイラに伝える
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
 
struct Config<'a> {
    name: &'a str,
    values: Vec<&'a str>,
}
 
impl<'a> Config<'a> {
    fn new(name: &'a str) -> Self {
        Config { name, values: Vec::new() }
    }
 
    fn add_value(&mut self, value: &'a str) {
        self.values.push(value);
    }
}
 
// Result/Option によるエラーハンドリング
use std::fs;
use std::io;
 
fn read_config(path: &str) -> Result<HashMap<String, String>, io::Error> {
    let content = fs::read_to_string(path)?;  // ? 演算子でエラー伝播
    let mut config = HashMap::new();
 
    for line in content.lines() {
        if let Some((key, value)) = line.split_once('=') {
            config.insert(key.trim().to_string(), value.trim().to_string());
        }
    }
 
    Ok(config)
}
 
// パターンマッチとenum(代数的データ型)
#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}
 
impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
            Shape::Rectangle { width, height } => width * height,
            Shape::Triangle { base, height } => base * height / 2.0,
        }
    }
}
 
// トレイト — インターフェースとジェネリクスの基盤
trait Summary {
    fn summarize(&self) -> String;
 
    // デフォルト実装
    fn summarize_short(&self) -> String {
        format!("{}...", &self.summarize()[..20])
    }
}
 
impl Summary for User {
    fn summarize(&self) -> String {
        format!("{} ({}歳)", self.name, self.age)
    }
}
 
// トレイト境界によるジェネリクス
fn print_summary(item: &impl Summary) {
    println!("{}", item.summarize());
}
 
// where句を使った複雑な制約
fn process_items<T>(items: &[T]) -> Vec<String>
where
    T: Summary + std::fmt::Debug,
{
    items.iter()
        .map(|item| item.summarize())
        .collect()
}
 
// 並行処理 — Send/Syncトレイトでコンパイル時安全性保証
use std::sync::{Arc, Mutex};
use std::thread;
 
fn concurrent_counter() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
 
    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
 
    for handle in handles {
        handle.join().unwrap();
    }
 
    println!("Counter: {}", *counter.lock().unwrap());  // 10
}
 
// チャネルによるメッセージパッシング
use std::sync::mpsc;
 
fn channel_example() {
    let (tx, rx) = mpsc::channel();
 
    // 送信側
    for i in 0..5 {
        let tx = tx.clone();
        thread::spawn(move || {
            tx.send(format!("Message {}", i)).unwrap();
        });
    }
    drop(tx);  // 元の送信者を閉じる
 
    // 受信側
    for received in rx {
        println!("Got: {}", received);
    }
}
 
// async/await(非同期処理)
use tokio;
 
#[tokio::main]
async fn main() {
    let urls = vec![
        "https://example.com",
        "https://example.org",
    ];
 
    let mut handles = vec![];
    for url in urls {
        handles.push(tokio::spawn(async move {
            let resp = reqwest::get(url).await.unwrap();
            (url.to_string(), resp.status().as_u16())
        }));
    }
 
    for handle in handles {
        let (url, status) = handle.await.unwrap();
        println!("{}: {}", url, status);
    }
}

2.4 Go — ガベージコレクタ + 軽量並行処理

package main
 
import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"
)
 
// Go: GCによるメモリ管理 + goroutine による軽量並行処理
// 設計哲学: シンプルさ、高速コンパイル、並行処理
 
// 構造体(Go にはクラスがない)
type User struct {
    Name string
    Age  int
    Tags []string
}
 
// メソッド(レシーバ付き関数)
func (u *User) AddTag(tag string) {
    u.Tags = append(u.Tags, tag)
}
 
func (u User) Display() string {
    return fmt.Sprintf("%s (age: %d, tags: %v)", u.Name, u.Age, u.Tags)
}
 
// インターフェース — 暗黙的実装(implements 宣言不要)
type Summarizer interface {
    Summarize() string
}
 
func (u User) Summarize() string {
    return fmt.Sprintf("%s (%d歳)", u.Name, u.Age)
}
 
// User は Summarizer を暗黙的に実装
func PrintSummary(s Summarizer) {
    fmt.Println(s.Summarize())
}
 
// エラーハンドリング — 明示的な error 値の返却
type AppError struct {
    Code    int
    Message string
}
 
func (e *AppError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
 
func FindUser(name string) (*User, error) {
    if name == "" {
        return nil, &AppError{Code: 400, Message: "name is required"}
    }
    if name == "Alice" {
        return &User{Name: "Alice", Age: 30}, nil
    }
    return nil, &AppError{Code: 404, Message: "user not found"}
}
 
// errors.Is / errors.As によるエラー判定(Go 1.13+)
func example() {
    user, err := FindUser("Bob")
    if err != nil {
        var appErr *AppError
        if errors.As(err, &appErr) {
            log.Printf("App error: code=%d, msg=%s", appErr.Code, appErr.Message)
        } else {
            log.Printf("Unknown error: %v", err)
        }
        return
    }
    fmt.Println(user.Display())
}
 
// Goroutine + Channel — Go の並行処理の核
func goroutineExample() {
    ch := make(chan string, 10)  // バッファ付きチャネル
 
    urls := []string{
        "https://example.com",
        "https://example.org",
        "https://example.net",
    }
 
    for _, url := range urls {
        go func(u string) {
            // HTTPリクエストを並行実行
            result := fmt.Sprintf("Fetched: %s", u)
            ch <- result
        }(url)
    }
 
    for range urls {
        fmt.Println(<-ch)
    }
}
 
// select文 — 複数チャネルの待機
func selectExample() {
    ch1 := make(chan string)
    ch2 := make(chan string)
 
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()
 
    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()
 
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(1 * time.Second):
            fmt.Println("timeout")
        }
    }
}
 
// Context によるキャンセレーション
func contextExample(ctx context.Context) error {
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()
 
    ch := make(chan string, 1)
    go func() {
        // 長時間処理
        time.Sleep(3 * time.Second)
        ch <- "done"
    }()
 
    select {
    case result := <-ch:
        fmt.Println(result)
        return nil
    case <-ctx.Done():
        return ctx.Err()  // context.DeadlineExceeded or context.Canceled
    }
}
 
// WaitGroup で複数 goroutine の完了を待機
func waitGroupExample() {
    var wg sync.WaitGroup
    results := make([]string, 5)
 
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(idx int) {
            defer wg.Done()
            results[idx] = fmt.Sprintf("result-%d", idx)
        }(i)
    }
 
    wg.Wait()
    fmt.Println(results)
}
 
// ジェネリクス(Go 1.18+)
func MapT any, U any U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = f(v)
    }
    return result
}
 
func FilterT any bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}
 
// 型制約
type Number interface {
    ~int | ~int64 | ~float64
}
 
func SumT Number T {
    var total T
    for _, n := range numbers {
        total += n
    }
    return total
}
 
// 使用例
func genericsExample() {
    names := []string{"Alice", "Bob", "Carol"}
    upper := Map(names, strings.ToUpper)
    // → ["ALICE", "BOB", "CAROL"]
 
    numbers := []int{1, 2, 3, 4, 5, 6}
    evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
    // → [2, 4, 6]
 
    total := Sum([]int{1, 2, 3, 4, 5})
    // → 15
}

2.5 Zig — コンパイル時計算と明示性

const std = @import("std");
const Allocator = std.mem.Allocator;
 
// Zig: 手動メモリ管理 + comptime(コンパイル時計算)
// 設計哲学: 隠れた制御フローなし、隠れたメモリ割り当てなし
 
// アロケータを明示的に渡す(隠れた割り当てなし)
const User = struct {
    name: []const u8,
    age: u32,
    tags: std.ArrayList([]const u8),
    allocator: Allocator,
 
    pub fn init(allocator: Allocator, name: []const u8, age: u32) User {
        return User{
            .name = name,
            .age = age,
            .tags = std.ArrayList([]const u8).init(allocator),
            .allocator = allocator,
        };
    }
 
    pub fn deinit(self: *User) void {
        self.tags.deinit();
    }
 
    pub fn addTag(self: *User, tag: []const u8) !void {
        try self.tags.append(tag);
    }
 
    pub fn display(self: User) void {
        std.debug.print("User: {s} (age: {})\n", .{ self.name, self.age });
    }
};
 
// エラーハンドリング — error union 型
const FileError = error{
    FileNotFound,
    PermissionDenied,
    OutOfMemory,
};
 
fn readConfig(path: []const u8) FileError![]const u8 {
    const file = std.fs.cwd().openFile(path, .{}) catch |err| switch (err) {
        error.FileNotFound => return FileError.FileNotFound,
        error.AccessDenied => return FileError.PermissionDenied,
        else => return FileError.FileNotFound,
    };
    defer file.close();
 
    // ファイル読み込み
    return file.readToEndAlloc(std.heap.page_allocator, 1024 * 1024) catch {
        return FileError.OutOfMemory;
    };
}
 
// comptime — コンパイル時計算(Zigの最大の特徴)
fn fibonacci(comptime n: u32) u32 {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}
 
// コンパイル時に計算されるので実行時コストゼロ
const fib_10 = fibonacci(10);  // コンパイル時に 55 と計算される
 
// comptime による型生成
fn Matrix(comptime T: type, comptime rows: usize, comptime cols: usize) type {
    return struct {
        data: [rows][cols]T,
 
        const Self = @This();
 
        pub fn init() Self {
            return Self{
                .data = [_][cols]T{[_]T{0} ** cols} ** rows,
            };
        }
 
        pub fn get(self: Self, row: usize, col: usize) T {
            return self.data[row][col];
        }
 
        pub fn set(self: *Self, row: usize, col: usize, value: T) void {
            self.data[row][col] = value;
        }
    };
}
 
// 使用例
const Mat3x3 = Matrix(f64, 3, 3);
 
pub fn main() void {
    var mat = Mat3x3.init();
    mat.set(0, 0, 1.0);
    mat.set(1, 1, 1.0);
    mat.set(2, 2, 1.0);
    // 3x3 単位行列
}
 
// defer / errdefer — リソース管理
fn processFile(path: []const u8) !void {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close();  // 関数終了時に必ず実行
 
    const buffer = try std.heap.page_allocator.alloc(u8, 4096);
    errdefer std.heap.page_allocator.free(buffer);  // エラー時のみ実行
 
    // ファイル処理...
}
 
// テスト(言語組み込み)
test "fibonacci" {
    try std.testing.expectEqual(fibonacci(0), 0);
    try std.testing.expectEqual(fibonacci(1), 1);
    try std.testing.expectEqual(fibonacci(10), 55);
}
 
test "user creation" {
    var user = User.init(std.testing.allocator, "Alice", 30);
    defer user.deinit();
 
    try user.addTag("admin");
    try std.testing.expectEqual(user.tags.items.len, 1);
}

3. エラーハンドリング戦略の比較

言語主なメカニズム特徴
C戻り値 + errnoエラーチェック漏れが起きやすい
情報量が少ない
C++例外 + RAIIスタックアンワインドで自動クリーンアップ
std::expected(C++23)noexcept で例外なし関数を明示
RustResult<T,E> +? 演算子で簡潔なエラー伝播
Option<T>panic! は回復不能エラーのみ
Goerror インターフェース(value, error) タプルで返却
errors.Is/As明示的だが冗長になりがち
Zigerror uniontry / catch で簡潔に記述
errdeferエラー時のクリーンアップが容易

同じエラーハンドリングパターンの比較

// C: 戻り値でエラーを伝える
#include <stdio.h>
#include <errno.h>
 
int read_int_from_file(const char* path, int* result) {
    FILE* f = fopen(path, "r");
    if (!f) {
        return -1;  // エラー(errno にエラーコード)
    }
 
    if (fscanf(f, "%d", result) != 1) {
        fclose(f);
        return -2;  // パースエラー
    }
 
    fclose(f);
    return 0;  // 成功
}
 
// 呼び出し側
void caller(void) {
    int value;
    int ret = read_int_from_file("config.txt", &value);
    if (ret == -1) {
        fprintf(stderr, "ファイルが開けません: %s\n", strerror(errno));
    } else if (ret == -2) {
        fprintf(stderr, "パースエラー\n");
    } else {
        printf("値: %d\n", value);
    }
}
// C++: 例外 + std::expected (C++23)
#include <expected>
#include <fstream>
#include <string>
 
enum class ReadError {
    FileNotFound,
    ParseError,
};
 
// C++23: std::expected
std::expected<int, ReadError> read_int_from_file(const std::string& path) {
    std::ifstream file(path);
    if (!file.is_open()) {
        return std::unexpected(ReadError::FileNotFound);
    }
 
    int value;
    if (!(file >> value)) {
        return std::unexpected(ReadError::ParseError);
    }
 
    return value;
}
 
// 呼び出し側
void caller() {
    auto result = read_int_from_file("config.txt");
    if (result.has_value()) {
        std::cout << "値: " << result.value() << std::endl;
    } else {
        switch (result.error()) {
            case ReadError::FileNotFound:
                std::cerr << "ファイルが見つかりません" << std::endl;
                break;
            case ReadError::ParseError:
                std::cerr << "パースエラー" << std::endl;
                break;
        }
    }
 
    // transform でチェーン
    auto doubled = read_int_from_file("config.txt")
        .transform( { return v * 2; });
}
// Rust: Result + ? 演算子
use std::fs;
use std::num::ParseIntError;
use thiserror::Error;
 
#[derive(Error, Debug)]
enum ReadError {
    #[error("ファイルが開けません: {0}")]
    FileNotFound(#[from] std::io::Error),
    #[error("パースエラー: {0}")]
    ParseError(#[from] ParseIntError),
}
 
fn read_int_from_file(path: &str) -> Result<i32, ReadError> {
    let content = fs::read_to_string(path)?;  // io::Error → ReadError
    let value: i32 = content.trim().parse()?;  // ParseIntError → ReadError
    Ok(value)
}
 
// 呼び出し側
fn caller() {
    match read_int_from_file("config.txt") {
        Ok(value) => println!("値: {}", value),
        Err(ReadError::FileNotFound(e)) => eprintln!("ファイルエラー: {}", e),
        Err(ReadError::ParseError(e)) => eprintln!("パースエラー: {}", e),
    }
 
    // map / and_then でチェーン
    let doubled = read_int_from_file("config.txt")
        .map(|v| v * 2);
}
// Go: error インターフェース
package main
 
import (
    "errors"
    "fmt"
    "os"
    "strconv"
    "strings"
)
 
type ReadError struct {
    Kind    string
    Message string
    Err     error
}
 
func (e *ReadError) Error() string {
    return fmt.Sprintf("%s: %s", e.Kind, e.Message)
}
 
func (e *ReadError) Unwrap() error {
    return e.Err
}
 
func readIntFromFile(path string) (int, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return 0, &ReadError{
            Kind:    "file_not_found",
            Message: fmt.Sprintf("ファイルが開けません: %s", path),
            Err:     err,
        }
    }
 
    value, err := strconv.Atoi(strings.TrimSpace(string(data)))
    if err != nil {
        return 0, &ReadError{
            Kind:    "parse_error",
            Message: "パースエラー",
            Err:     err,
        }
    }
 
    return value, nil
}
 
// 呼び出し側
func caller() {
    value, err := readIntFromFile("config.txt")
    if err != nil {
        var readErr *ReadError
        if errors.As(err, &readErr) {
            fmt.Printf("エラー種別: %s, メッセージ: %s\n", readErr.Kind, readErr.Message)
        } else {
            fmt.Printf("不明なエラー: %v\n", err)
        }
        return
    }
    fmt.Printf("値: %d\n", value)
}

4. ビルドシステムとツールチェーン

言語ビルドツール特徴
CMake, CMake歴史的に最も使われている
Meson, Ninjaビルドスクリプトの記述が複雑
プラットフォーム依存が大きい
C++CMake事実上の標準だが設定が難解
Bazel大規模プロジェクト向け(Google製)
Conan,vcpkgパッケージマネージャ
RustCargoビルド+パッケージ+テスト+ベンチ統合
toml設定、cargo.lock で再現性確保
最も優れたツールチェーン体験
Gogo buildgo mod でモジュール管理
外部ツール不要、go コマンドで完結
クロスコンパイルが非常に簡単
Zigzig buildbuild.zig で宣言的ビルド
C/C++ のクロスコンパイラとしても使用可能
libc を同梱
# Rust: Cargo.toml — 依存管理の模範例
[package]
name = "my-cli-tool"
version = "0.1.0"
edition = "2021"
rust-version = "1.75"
 
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"
tracing = "0.1"
 
[dev-dependencies]
assert_cmd = "2"
predicates = "3"
tempfile = "3"
 
[profile.release]
lto = true        # リンク時最適化
strip = true      # デバッグ情報除去
codegen-units = 1 # 最大最適化
// Go: go.mod — シンプルなモジュール管理
// go.mod
module github.com/user/myapp
 
go 1.22
 
require (
    github.com/gin-gonic/gin v1.9.1
    github.com/jackc/pgx/v5 v5.5.3
    go.uber.org/zap v1.27.0
)
 
// クロスコンパイル(コマンド1つ)
// GOOS=linux GOARCH=amd64 go build -o myapp-linux
// GOOS=darwin GOARCH=arm64 go build -o myapp-mac
// GOOS=windows GOARCH=amd64 go build -o myapp.exe

5. 適用領域の詳細

5.1 領域別最適言語マッピング

OS カーネル:
  C:    Linux kernel(3,000万行超)
  Rust: Linux kernel 新モジュール(6.1から公式サポート)
  C++:  Windows kernel の一部

ブラウザエンジン:
  C++:  Chromium (Blink), WebKit
  Rust: Firefox (Servo コンポーネント → Stylo CSS エンジン)

データベース:
  C:    SQLite, PostgreSQL
  C++:  MySQL, MongoDB, RocksDB, ClickHouse
  Rust: SurrealDB, TiKV, Neon (PostgreSQL互換)
  Go:   CockroachDB, TiDB, InfluxDB

ゲームエンジン:
  C++:  Unreal Engine, Unity (C# + C++内部)
  Rust: Bevy (新興だが成長中)
  Zig:  一部のインディーゲームエンジン

クラウドインフラ:
  Go:   Docker, Kubernetes, Terraform, Prometheus, Grafana, etcd
  Rust: Firecracker (AWS Lambda基盤), Bottlerocket, Linkerd2-proxy

CLI ツール:
  Rust: ripgrep, bat, fd, exa/eza, starship, zoxide, delta
  Go:   gh (GitHub CLI), lazygit, fzf, Hugo, k9s

暗号・セキュリティ:
  Rust: BoringSSL一部, rustls
  C:    OpenSSL, libsodium
  Go:   crypto/tls (標準ライブラリ)

組み込み/IoT:
  C:    圧倒的シェア(FreeRTOS, Zephyr の一部)
  Rust: Embassy (async embedded), RTIC
  Zig:  組み込みLinux, マイクロコントローラ

WebAssembly:
  Rust: Yew, Leptos, wasm-bindgen
  C/C++: Emscripten
  Go:   TinyGo (最適化版)
  Zig:  ネイティブWasm出力

5.2 パフォーマンスベンチマーク

Benchmark: HTTP サーバー(requests/sec, 高いほど良い)
  C (epoll直接):  500,000+
  Rust (actix):   400,000+
  Go (net/http):  200,000+
  C++ (drogon):   350,000+
  Zig (zap):      450,000+

Benchmark: JSON パース(1GB ファイル)
  C (simdjson):   2.5 GB/s
  Rust (simd-json): 2.3 GB/s
  C++ (simdjson): 2.5 GB/s
  Go (encoding):  0.3 GB/s
  Go (sonic):     1.5 GB/s

Benchmark: コンパイル時間(中規模プロジェクト)
  Go:    2-5 秒
  C:     5-15 秒
  Zig:   5-15 秒
  Rust:  30-120 秒(増分は10-30秒)
  C++:   60-300 秒

Benchmark: バイナリサイズ(Hello World)
  C:     16 KB (static: 800 KB)
  Go:    1.8 MB (static by default)
  Rust:  300 KB (stripped)
  Zig:   5 KB (stripped)
  C++:   20 KB (dynamic)

※ 実際のパフォーマンスはワークロードに大きく依存する
※ ベンチマークは参考値として捉えること

6. メモリ安全性の議論

6.1 米国政府の勧告(2024年)

2024年2月、米国ホワイトハウスは「メモリ安全な言語への移行」を勧告:
- C/C++ のメモリ関連脆弱性がサイバー攻撃の主因
- CVE の約70%がメモリ安全性に起因
- Rust, Go, Java, C# 等のメモリ安全な言語を推奨

影響:
- Linux kernel への Rust 採用が加速
- Android の新規コードにおける Rust 比率が増加
- DARPA の TRACTOR プログラム(C → Rust 自動変換研究)
- NSA のサイバーセキュリティガイダンスで Rust を推奨

6.2 各言語のメモリ安全性メカニズム

C:
  ✗ バッファオーバーフロー
  ✗ Use After Free
  ✗ ダブルフリー
  ✗ Null ポインタ参照
  △ AddressSanitizer, Valgrind で動的検出

C++:
  △ スマートポインタで一部解決
  △ RAII でリソース漏れを防止
  ✗ 生ポインタの unsafe な操作は依然可能
  △ 静的解析ツール(Clang-Tidy, PVS-Studio)

Rust:
  ✓ 所有権 + 借用チェッカーでコンパイル時に保証
  ✓ Null なし(Option<T> で表現)
  ✓ データ競合なし(Send/Sync トレイト)
  △ unsafe ブロック内は保証なし(最小限に抑える)

Go:
  ✓ GC でメモリリーク/ダングリングポインタなし
  ✓ 境界チェックあり(ランタイム)
  △ Race detector(動的検出)
  ✗ Null ポインタ(nil)によるパニックは可能

Zig:
  △ 手動管理だがアロケータの明示で追跡が容易
  △ テストアロケータでリーク検出
  △ undefined behavior はあるが最小限
  ✓ 安全性チェックをビルドモードで制御可能

7. 並行処理モデルの詳細比較

CPOSIX threads (pthread)
- 低レベル、OS スレッドを直接操作
- mutex, condition variable, semaphore
- エラーが起きやすい(デッドロック、レース)
C++std::thread + std::async (C++11)
std::jthread (C++20, 自動join)
- std::mutex, std::shared_mutex
- std::atomic で lock-free プログラミング
- coroutines (C++20)
Ruststd::thread + crossbeam
- Send/Sync トレイトでコンパイル時安全性
- Arc<Mutex<T>> で共有状態
- mpsc チャネル、crossbeam チャネル
- async/await (tokio, async-std)
- Rayon(データ並列処理)
Gogoroutine + channel
- 軽量(初期 2KB スタック、動的拡張)
- 数百万の goroutine を実行可能
- select 文で複数チャネルを待機
- "Don't communicate by sharing memory;
share memory by communicating"
Zigasync/await(言語組み込み)
- イベント駆動 I/O
- std.Thread で OS スレッド
- アロケータを通じた制御

8. 実践的なプロジェクト構成例

8.1 Rust CLI プロジェクト

my-cli/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs           # エントリポイント
│   ├── lib.rs            # ライブラリルート
│   ├── cli.rs            # clap によるCLI定義
│   ├── config.rs         # 設定管理
│   ├── commands/
│   │   ├── mod.rs
│   │   ├── init.rs       # init サブコマンド
│   │   └── run.rs        # run サブコマンド
│   ├── core/
│   │   ├── mod.rs
│   │   ├── engine.rs     # コアロジック
│   │   └── types.rs      # 型定義
│   └── utils/
│       ├── mod.rs
│       └── fs.rs         # ファイルシステムユーティリティ
├── tests/
│   ├── integration_test.rs
│   └── fixtures/
├── benches/
│   └── benchmark.rs      # criterion によるベンチマーク
└── .github/
    └── workflows/
        └── ci.yml

8.2 Go Web API プロジェクト

myapp/
├── go.mod
├── go.sum
├── cmd/
│   └── server/
│       └── main.go        # エントリポイント
├── internal/              # 外部から import 不可
│   ├── handler/
│   │   ├── user.go        # ユーザーハンドラ
│   │   └── middleware.go  # ミドルウェア
│   ├── service/
│   │   └── user.go        # ビジネスロジック
│   ├── repository/
│   │   └── user.go        # データアクセス
│   ├── model/
│   │   └── user.go        # ドメインモデル
│   └── config/
│       └── config.go      # 設定
├── pkg/                   # 外部から import 可
│   └── response/
│       └── json.go
├── migrations/
│   └── 001_create_users.sql
├── Dockerfile
├── Makefile
└── .github/
    └── workflows/
        └── ci.yml

9. 選択指針の詳細フローチャート

Q1: GC のポーズが許容できるか?
├── はい → Q2
└── いいえ → Q3

Q2: シンプルさと高速コンパイルが重要か?
├── はい → Go
│   適用: マイクロサービス, CLI, DevOpsツール
│   利点: 学習が容易、チーム全体で統一しやすい
└── いいえ → Q4

Q3: メモリ安全性が必要か?
├── はい → Rust
│   適用: インフラ, セキュリティ, Wasm, CLI
│   利点: コンパイル時安全性保証、ゼロコスト抽象化
└── いいえ → Q5

Q4: 関数型プログラミングやジェネリクスを多用するか?
├── はい → Rust
│   適用: ライブラリ, フレームワーク, 言語ツール
└── いいえ → Go
    適用: CRUD API, ネットワークサービス

Q5: 既存の C/C++ コードベースとの統合が必要か?
├── C++ コードベース → C++
│   適用: ゲーム, ブラウザ, 既存システムの拡張
├── C コードベース → C or Zig
│   Zig は C ヘッダを直接 import 可能
└── 新規プロジェクト → Rust or Zig
    Zig: C の代替、組み込み特化

Q6: ゲーム開発か?
├── はい → C++(Unreal)or Rust(Bevy)
└── いいえ → 上記フローに従う

よくある誤解と補正

誤解: 「Go は遅い」
現実: GC あるが HTTP サーバーでは十分高速。多くの用途で C++ を選ぶ必要はない。

誤解: 「Rust は難しすぎて実用的でない」
現実: 学習曲線は急だが、慣れれば生産性は高い。
      借用チェッカーに慣れるまでの2-4週間が山場。

誤解: 「C は古くて使うべきでない」
現実: 組み込み、カーネル、特殊なシステムでは今でも最適な選択肢。
      ABI の安定性はCが最も優れている。

誤解: 「C++ は複雑すぎる」
現実: Modern C++ (C++17/20/23) は大幅に改善。
      ただし全機能を使う必要はない。プロジェクトで使用する機能を限定すべき。

誤解: 「Zig はまだ実験的」
現実: 本番利用が増加中。Uber の bazel-zig-cc、
      Bun (JavaScript ランタイム) が Zig で書かれている。

10. 学習リソースとロードマップ

C:
  入門: K&R "The C Programming Language"
  実践: "Expert C Programming"
  期間: 基本文法 2週間、ポインタ習得 1-2ヶ月

C++:
  入門: "A Tour of C++" (Stroustrup)
  実践: "Effective Modern C++" (Meyers)
  期間: 基本文法 1ヶ月、Modern C++ 習得 3-6ヶ月

Rust:
  入門: "The Rust Programming Language" (公式Book)
  実践: "Rust in Action", "Zero To Production"
  期間: 基本文法 2-3週間、借用チェッカー克服 1-2ヶ月

Go:
  入門: "The Go Programming Language" (Donovan & Kernighan)
  実践: "Let's Go" (Alex Edwards)
  期間: 基本文法 1-2週間、実務レベル 1-2ヶ月

Zig:
  入門: ziglearn.org, "Zig Guide"
  実践: zig.guide, std ライブラリのソースコード
  期間: 基本文法 2-3週間、comptime 習得 1-2ヶ月

実践演習

演習1: 基本的な実装

以下の要件を満たすコードを実装してください。

要件:

  • 入力データの検証を行うこと
  • エラーハンドリングを適切に実装すること
  • テストコードも作成すること
# 演習1: 基本実装のテンプレート
class Exercise1:
    """基本的な実装パターンの演習"""
 
    def __init__(self):
        self.data = []
 
    def validate_input(self, value):
        """入力値の検証"""
        if value is None:
            raise ValueError("入力値がNoneです")
        return True
 
    def process(self, value):
        """データ処理のメインロジック"""
        self.validate_input(value)
        self.data.append(value)
        return self.data
 
    def get_results(self):
        """処理結果の取得"""
        return {
            'count': len(self.data),
            'data': self.data
        }
 
# テスト
def test_exercise1():
    ex = Exercise1()
    assert ex.process(1) == [1]
    assert ex.process(2) == [1, 2]
    assert ex.get_results()['count'] == 2
 
    try:
        ex.process(None)
        assert False, "例外が発生するべき"
    except ValueError:
        pass
 
    print("全テスト合格!")
 
test_exercise1()

演習2: 応用パターン

基本実装を拡張して、以下の機能を追加してください。

# 演習2: 応用パターン
from typing import List, Dict, Optional
from datetime import datetime
 
class AdvancedExercise:
    """応用パターンの演習"""
 
    def __init__(self, max_size: int = 100):
        self._items: List[Dict] = []
        self._max_size = max_size
        self._created_at = datetime.now()
 
    def add(self, key: str, value: any) -> bool:
        """アイテムの追加(サイズ制限付き)"""
        if len(self._items) >= self._max_size:
            return False
        self._items.append({
            'key': key,
            'value': value,
            'timestamp': datetime.now().isoformat()
        })
        return True
 
    def find(self, key: str) -> Optional[Dict]:
        """キーによる検索"""
        for item in reversed(self._items):
            if item['key'] == key:
                return item
        return None
 
    def remove(self, key: str) -> bool:
        """キーによる削除"""
        for i, item in enumerate(self._items):
            if item['key'] == key:
                self._items.pop(i)
                return True
        return False
 
    def stats(self) -> Dict:
        """統計情報"""
        return {
            'total_items': len(self._items),
            'max_size': self._max_size,
            'usage_percent': len(self._items) / self._max_size * 100,
            'uptime': str(datetime.now() - self._created_at)
        }
 
# テスト
def test_advanced():
    ex = AdvancedExercise(max_size=3)
    assert ex.add("a", 1) == True
    assert ex.add("b", 2) == True
    assert ex.add("c", 3) == True
    assert ex.add("d", 4) == False  # サイズ制限
    assert ex.find("b")['value'] == 2
    assert ex.remove("b") == True
    assert ex.find("b") is None
    stats = ex.stats()
    assert stats['total_items'] == 2
    print("応用テスト全合格!")
 
test_advanced()

演習3: パフォーマンス最適化

以下のコードのパフォーマンスを改善してください。

# 演習3: パフォーマンス最適化
import time
from functools import lru_cache
 
# 最適化前(O(n^2))
def slow_search(data: list, target: int) -> int:
    """非効率な検索"""
    for i in range(len(data)):
        for j in range(i + 1, len(data)):
            if data[i] + data[j] == target:
                return (i, j)
    return (-1, -1)
 
# 最適化後(O(n))
def fast_search(data: list, target: int) -> tuple:
    """ハッシュマップを使った効率的な検索"""
    seen = {}
    for i, num in enumerate(data):
        complement = target - num
        if complement in seen:
            return (seen[complement], i)
        seen[num] = i
    return (-1, -1)
 
# ベンチマーク
def benchmark():
    import random
    data = list(range(5000))
    random.shuffle(data)
    target = data[100] + data[4000]
 
    start = time.time()
    result1 = slow_search(data, target)
    slow_time = time.time() - start
 
    start = time.time()
    result2 = fast_search(data, target)
    fast_time = time.time() - start
 
    print(f"非効率版: {slow_time:.4f}秒")
    print(f"効率版:   {fast_time:.6f}秒")
    print(f"高速化率: {slow_time/fast_time:.0f}倍")
 
benchmark()

ポイント:

  • アルゴリズムの計算量を意識する
  • 適切なデータ構造を選択する
  • ベンチマークで効果を測定する

FAQ

Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?

実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。

Q2: 初心者がよく陥る間違いは何ですか?

基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。

Q3: 実務ではどのように活用されていますか?

このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。


まとめ

言語 哲学 最適なユースケース 2025年の状況
C 最小限の抽象化 OS, カーネル, 組み込み 不動の地位。ABI の lingua franca
C++ ゼロコスト抽象化 ゲーム, ブラウザ, DB C++23 で大幅改善。依然として巨大
Rust 安全性+パフォーマンス インフラ, CLI, Wasm 急成長。Linux kernel 採用で正統性確立
Go シンプルさ+並行処理 クラウド, マイクロサービス クラウドインフラの事実上の標準言語
Zig C のモダン代替 組み込み, システム Bun で知名度上昇。C の後継候補

次に読むべきガイド


参考文献

  1. Blandy, J., Orendorff, J. & Tindall, L. "Programming Rust." 2nd Ed, O'Reilly, 2021.
  2. Donovan, A. & Kernighan, B. "The Go Programming Language." Addison-Wesley, 2015.
  3. Stroustrup, B. "A Tour of C++." 3rd Ed, Addison-Wesley, 2022.
  4. Kernighan, B. & Ritchie, D. "The C Programming Language." 2nd Ed, Prentice Hall, 1988.
  5. Klabnik, S. & Nichols, C. "The Rust Programming Language." No Starch Press, 2023.
  6. "The White House: Back to the Building Blocks." Technical Report, 2024.
  7. "Rust for Linux." rust-for-linux.com.
  8. "State of Developer Ecosystem 2024." JetBrains.
  9. Kelley, A. "The Zig Programming Language." ziglang.org.
  10. "Benchmarks Game." benchmarksgame-team.pages.debian.net.