Odin言語入門 - Cの代替を目指す新しいシステムプログラミング言語
Odin言語入門 - Cの代替を目指す新しいシステムプログラミング言語
Odinは、ゲーム開発者Ginger Billによって開発された、シンプルで高速なシステムプログラミング言語です。C言語の直接的な後継を目指し、シンプルさと表現力を兼ね備えた設計思想が特徴です。
Odinとは
Odin言語は以下の理念に基づいて設計されています。
設計哲学
- シンプルさ: 言語機能を最小限に抑える
- 高速性: Cと同等のパフォーマンス
- 実用性: 実際のプロダクション開発に使える
- 明示性: 暗黙の動作を避ける
- 喜び: プログラミングを楽しくする
主な特徴
- 手動メモリ管理: 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を試してみてください。