Odin言語入門 - Cの代替を目指す新しいシステムプログラミング言語


Odin言語入門 - Cの代替を目指す新しいシステムプログラミング言語

Odinは、ゲーム開発者Ginger Billによって開発された、シンプルで高速なシステムプログラミング言語です。C言語の直接的な後継を目指し、シンプルさと表現力を兼ね備えた設計思想が特徴です。

Odinとは

Odin言語は以下の理念に基づいて設計されています。

設計哲学

  1. シンプルさ: 言語機能を最小限に抑える
  2. 高速性: Cと同等のパフォーマンス
  3. 実用性: 実際のプロダクション開発に使える
  4. 明示性: 暗黙の動作を避ける
  5. 喜び: プログラミングを楽しくする

主な特徴

  • 手動メモリ管理: GCなし、完全なコントロール
  • シンプルな構文: Cより読みやすく書きやすい
  • コンパイル時実行: コンパイル時にコードを実行可能
  • パッケージシステム: モダンなモジュール管理
  • 並行処理: 軽量なコンテキストスイッチング
  • 相互運用性: CとのシームレスなFFI

なぜOdinか

C言語の問題点:
- 複雑なヘッダーシステム
- マクロの乱用
- 型システムの弱さ
- モダンな機能の欠如

Odinの解決策:
- シンプルなパッケージシステム
- 強力な型システム
- 組み込みの配列・スライス
- コンパイル時プログラミング

インストール

前提条件

  • Windows: MSVC または MinGW
  • macOS: Xcode Command Line Tools
  • Linux: GCC または Clang

インストール手順

# リポジトリをクローン
git clone https://github.com/odin-lang/Odin
cd Odin

# ビルド
# macOS/Linux
make

# Windows (MSVC)
build.bat

# パスを通す
export PATH=$PATH:$PWD

# インストール確認
odin version

Hello World

package main

import "core:fmt"

main :: proc() {
    fmt.println("Hello, Odin!")
}
# コンパイルして実行
odin run hello.odin

# または、実行ファイルを生成
odin build hello.odin
./hello

基本構文

変数宣言

package main

import "core:fmt"

main :: proc() {
    // 型推論
    x := 42
    name := "Odin"

    // 明示的な型指定
    y: int = 100
    pi: f32 = 3.14

    // 複数変数の同時宣言
    a, b := 10, 20

    // 定数
    MAX_SIZE :: 1024

    fmt.println(x, name, y, pi, a, b)
}

基本型

// 整数型
i8, i16, i32, i64, i128    // 符号付き整数
u8, u16, u32, u64, u128    // 符号なし整数
int                         // プラットフォーム依存(32 or 64 bit)
uint                        // 符号なしint

// 浮動小数点型
f16, f32, f64              // IEEE 754浮動小数点

// 複素数型
complex32, complex64, complex128

// 真偽値
bool                        // true or false

// 文字型
rune                        // UTF-32文字(i32のエイリアス)

// 文字列
string                      // UTF-8文字列(不変)
cstring                     // C言語互換のヌル終端文字列

制御構文

package main

import "core:fmt"

main :: proc() {
    // if文
    x := 10
    if x > 5 {
        fmt.println("x is greater than 5")
    } else if x < 5 {
        fmt.println("x is less than 5")
    } else {
        fmt.println("x is 5")
    }

    // if文で変数宣言
    if y := x * 2; y > 15 {
        fmt.println("y is", y)
    }

    // for文(唯一のループ構文)
    // 範囲ループ
    for i in 0..<5 {
        fmt.println(i) // 0, 1, 2, 3, 4
    }

    // 条件ループ
    count := 0
    for count < 3 {
        fmt.println(count)
        count += 1
    }

    // 無限ループ
    for {
        fmt.println("infinite loop")
        break // 抜ける
    }

    // switch文
    switch x {
    case 1:
        fmt.println("one")
    case 2, 3:
        fmt.println("two or three")
    case 10:
        fmt.println("ten")
    case:
        fmt.println("other")
    }

    // 型スイッチ
    value: any = 42
    switch v in value {
    case int:
        fmt.println("int:", v)
    case string:
        fmt.println("string:", v)
    case:
        fmt.println("unknown type")
    }
}

プロシージャ(関数)

基本的なプロシージャ

package main

import "core:fmt"

// シンプルなプロシージャ
greet :: proc(name: string) {
    fmt.printf("Hello, %s!\n", name)
}

// 戻り値あり
add :: proc(a, b: int) -> int {
    return a + b
}

// 複数の戻り値
div_mod :: proc(a, b: int) -> (quotient: int, remainder: int) {
    quotient = a / b
    remainder = a % b
    return // 名前付き戻り値は自動的に返される
}

// デフォルト引数
power :: proc(base: int, exponent: int = 2) -> int {
    result := 1
    for i in 0..<exponent {
        result *= base
    }
    return result
}

main :: proc() {
    greet("Odin")

    sum := add(10, 20)
    fmt.println("Sum:", sum)

    q, r := div_mod(17, 5)
    fmt.printf("17 / 5 = %d remainder %d\n", q, r)

    fmt.println("2^2 =", power(2))      // デフォルト引数使用
    fmt.println("2^8 =", power(2, 8))   // 明示的指定
}

可変長引数

package main

import "core:fmt"

sum :: proc(numbers: ..int) -> int {
    total := 0
    for n in numbers {
        total += n
    }
    return total
}

main :: proc() {
    fmt.println(sum(1, 2, 3))           // 6
    fmt.println(sum(10, 20, 30, 40))    // 100
}

プロシージャのオーバーロード

package main

import "core:fmt"

// 整数版
print_value :: proc(value: int) {
    fmt.printf("Int: %d\n", value)
}

// 浮動小数点版
print_value :: proc(value: f64) {
    fmt.printf("Float: %.2f\n", value)
}

// 文字列版
print_value :: proc(value: string) {
    fmt.printf("String: %s\n", value)
}

main :: proc() {
    print_value(42)
    print_value(3.14)
    print_value("Hello")
}

データ構造

配列

package main

import "core:fmt"

main :: proc() {
    // 固定長配列
    numbers: [5]int = {1, 2, 3, 4, 5}

    // 自動サイズ推論
    colors := [?]string{"Red", "Green", "Blue"}

    // アクセス
    fmt.println(numbers[0])  // 1
    fmt.println(len(colors)) // 3

    // イテレーション
    for num, i in numbers {
        fmt.printf("numbers[%d] = %d\n", i, num)
    }
}

スライス

package main

import "core:fmt"
import "core:slice"

main :: proc() {
    // 動的配列(スライス)
    numbers: [dynamic]int
    defer delete(numbers) // メモリ解放を忘れずに

    // 要素の追加
    append(&numbers, 1)
    append(&numbers, 2, 3, 4)

    fmt.println(numbers) // [1, 2, 3, 4]

    // スライスの生成
    slice_part := numbers[1:3] // [2, 3]
    fmt.println(slice_part)

    // ソート
    slice.sort(numbers[:])

    // 検索
    found := slice.contains(numbers[:], 3)
    fmt.println("Contains 3:", found)
}

マップ

package main

import "core:fmt"

main :: proc() {
    // マップの作成
    ages := make(map[string]int)
    defer delete(ages) // メモリ解放

    // 要素の追加
    ages["Alice"] = 30
    ages["Bob"] = 25
    ages["Charlie"] = 35

    // アクセス
    fmt.println("Alice's age:", ages["Alice"])

    // 存在確認
    if age, ok := ages["David"]; ok {
        fmt.println("David's age:", age)
    } else {
        fmt.println("David not found")
    }

    // イテレーション
    for name, age in ages {
        fmt.printf("%s is %d years old\n", name, age)
    }

    // 削除
    delete_key(&ages, "Bob")
}

構造体

package main

import "core:fmt"

// 構造体定義
Person :: struct {
    name: string,
    age: int,
    email: string,
}

// メソッド(レシーバー付きプロシージャ)
greet :: proc(p: Person) {
    fmt.printf("Hello, I'm %s, %d years old\n", p.name, p.age)
}

// ポインタレシーバー
celebrate_birthday :: proc(p: ^Person) {
    p.age += 1
    fmt.printf("Happy Birthday! Now %d years old\n", p.age)
}

main :: proc() {
    // 構造体の初期化
    alice := Person{
        name = "Alice",
        age = 30,
        email = "alice@example.com",
    }

    greet(alice)
    celebrate_birthday(&alice)
    greet(alice) // age が 31 になっている
}

メモリ管理

アロケータ

package main

import "core:fmt"
import "core:mem"

main :: proc() {
    // デフォルトアロケータ
    data := new(int)
    defer free(data)
    data^ = 42

    // アリーナアロケータ
    arena: mem.Arena
    mem.arena_init(&arena, make([]byte, 1024))
    defer mem.arena_destroy(&arena)

    arena_allocator := mem.arena_allocator(&arena)

    context.allocator = arena_allocator

    // このスコープ内の全アロケーションはアリーナから
    numbers := make([dynamic]int, 0, 10)
    append(&numbers, 1, 2, 3)

    // arenaが破棄されると全て解放される
}

一時アロケータ

package main

import "core:fmt"

temp_concat :: proc(strs: []string) -> string {
    // 一時的なメモリを使用
    context.allocator = context.temp_allocator

    result: [dynamic]u8
    for str in strs {
        append(&result, ..transmute([]u8)str)
    }

    return string(result[:])
}

main :: proc() {
    defer free_all(context.temp_allocator)

    words := []string{"Hello", " ", "World"}
    message := temp_concat(words)
    fmt.println(message)

    // temp_allocatorのメモリは関数終了時に解放
}

パッケージシステム

パッケージ構成

my_project/
├── main.odin
├── math/
│   ├── vector.odin
│   └── matrix.odin
└── utils/
    └── helpers.odin

vector.odin

package math

Vec2 :: struct {
    x, y: f32,
}

vec2_add :: proc(a, b: Vec2) -> Vec2 {
    return Vec2{a.x + b.x, a.y + b.y}
}

vec2_length :: proc(v: Vec2) -> f32 {
    return sqrt(v.x*v.x + v.y*v.y)
}

main.odin

package main

import "core:fmt"
import "math" // ローカルパッケージ

main :: proc() {
    v1 := math.Vec2{1, 2}
    v2 := math.Vec2{3, 4}

    result := math.vec2_add(v1, v2)
    length := math.vec2_length(result)

    fmt.printf("Result: (%.2f, %.2f), Length: %.2f\n",
               result.x, result.y, length)
}

コンパイル時プログラミング

コンパイル時定数

package main

import "core:fmt"

// コンパイル時計算
ARRAY_SIZE :: 10
BUFFER_SIZE :: ARRAY_SIZE * 4

// コンパイル時アサーション
#assert(BUFFER_SIZE == 40)

main :: proc() {
    buffer: [BUFFER_SIZE]u8
    fmt.println("Buffer size:", len(buffer))
}

コンパイル時実行

package main

import "core:fmt"

// コンパイル時に実行される関数
factorial :: proc(n: int) -> int {
    if n <= 1 do return 1
    return n * factorial(n - 1)
}

FACT_5 :: #run factorial(5) // コンパイル時に計算

main :: proc() {
    fmt.println("5! =", FACT_5) // 120
}

実践例: HTTPサーバー

package main

import "core:fmt"
import "core:net"
import "core:strings"

handle_request :: proc(conn: net.TCP_Socket) {
    buffer: [1024]byte
    n, err := net.recv_tcp(conn, buffer[:])
    if err != nil {
        fmt.eprintln("Error reading:", err)
        return
    }

    request := string(buffer[:n])
    fmt.println("Received:", request)

    response := "HTTP/1.1 200 OK\r\n" +
                "Content-Type: text/html\r\n" +
                "\r\n" +
                "<h1>Hello from Odin!</h1>"

    net.send_tcp(conn, transmute([]byte)response)
    net.close(conn)
}

main :: proc() {
    endpoint := net.Endpoint{
        address = net.IP4_Address{127, 0, 0, 1},
        port = 8080,
    }

    socket, err := net.listen_tcp(endpoint)
    if err != nil {
        fmt.eprintln("Failed to start server:", err)
        return
    }
    defer net.close(socket)

    fmt.println("Server listening on http://127.0.0.1:8080")

    for {
        conn, _, accept_err := net.accept_tcp(socket)
        if accept_err != nil {
            fmt.eprintln("Accept error:", accept_err)
            continue
        }

        handle_request(conn)
    }
}

C言語との相互運用

CからOdinを呼ぶ

package mathlib

foreign export add

add :: proc "c" (a, b: i32) -> i32 {
    return a + b
}
// C側
#include <stdio.h>

extern int add(int a, int b);

int main() {
    int result = add(10, 20);
    printf("Result: %d\n", result);
    return 0;
}

OdinからCライブラリを呼ぶ

package main

import "core:fmt"
import "core:c"

foreign import libc "system:c"

@(default_calling_convention="c")
foreign libc {
    printf :: proc(format: cstring, #c_vararg args: ..any) -> c.int ---
    sqrt :: proc(x: c.double) -> c.double ---
}

main :: proc() {
    x := 16.0
    result := sqrt(x)
    printf("sqrt(%.2f) = %.2f\n", x, result)
}

まとめ

Odin言語は、C言語の後継として以下の利点を提供します。

主な強み

  • シンプルさ: 理解しやすく、学習曲線が緩やか
  • 高速: Cと同等のパフォーマンス
  • 現代的: モダンな言語機能を備える
  • 実用的: 実際のプロダクション開発に使える

適用分野

  • ゲーム開発
  • システムプログラミング
  • 組み込みシステム
  • 高性能コンピューティング
  • デバイスドライバ

Odinは、C言語の直接性とパフォーマンスを保ちながら、より書きやすく保守しやすいコードを実現します。次のシステムプログラミングプロジェクトで、Odinを試してみてください。