Iori's Blog

Goの勉強中に感じたRustとの違い

Published on: Thu Jul 09 2026 00:00:00 GMT+0000 (Coordinated Universal Time)

最近は就活や授業で忙しく、全く記事を更新できていませんが、今やっていることをぽちぽち書いていこうと思います。

Goの勉強

最近は就活に勤しんでいるのですが、やっと夏休みに参加するインターンが決まりました。 普段はRustを使用しているのですが、インターン先ではGoを使うとのことだったので、 ぼちぼちGoの勉強を進めています(まあ面談の時にGoの勉強をしといたほうがいいよと言われただけなので実際に使うかは分かりませんが)。

そこでこの記事では自分が一番分かっているRustとの違いや、感じたことなどを勉強を兼ねて書き記しておこうと思います。

勉強サイト

A Tour of Goを使って勉強しています。

とりあえずMethods and interfacesまでは軽く触ってみたので、 ここからは実際に何か作ってみようということで、 CLIのリマインダーアプリを作ってみようと思っています。

iorinu/simple-reminder
0

違い

エラーハンドリング

書いてて一番違いを感じるのはエラーハンドリングの助長さが気になります。 これはGoの思想ではあるので当たり前ではありますが、 毎回毎回if文でエラーハンドリングをしなければいけないのは少し面倒に感じます。 例えばファイルを読み込んで内容を出力するだけの処理でも、GoとRustではエラーハンドリングの見た目がかなり違います。

use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // ?演算子でエラーを呼び出し元に伝播できる
    let content = fs::read_to_string("hello.txt")?;
    println!("{}", content);
    Ok(())
}
package main

import (
	"fmt"
	"os"
)

func main() {
	// 関数を呼ぶたびに if err != nil でチェックが必要
	content, err := os.ReadFile("hello.txt")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println(string(content))
}

Rustでは?演算子でエラーを呼び出し元に伝播できるので一行で済むところが、Goだと毎回if err != nil { ... }を書く必要があり、処理が増えるほどこの記述が積み重なっていきます。

標準パッケージの豊富さとモジュールの扱い

Goは標準パッケージでかなりのことができると感じています。 一番面白いと感じているのは非同期処理をするものが標準ライブラリに含まれていることです。 他の言語では分かりませんが、少なくともRustでは外部クレートであるtokioは必須みたいな感じではあると思うので驚きました。 リマインダーを作成している今現在では外部ライブラリはCLIのコマンドを簡単に作るもののみで作れそうです(このあと増える可能性はありますが)。

Option型がない

Rustでは値があるかもしれないし、ないかもしれないような状態を表せるOption型がありますが、Goにはそのようなものはありません。 今回リマインダーでは、リマインド時刻の部分をOption型で実装したかったのですが不可能です。 そのため以下のような実装になっています。

type Reminder struct {
    ID        int64
    Title     string
    RemindAt  *time.Time // リマインド時刻。未設定の場合は nil
    Done      bool
    CreatedAt time.Time
}

// 使う側
if r.RemindAt != nil {
    fmt.Println("リマインド時刻:", r.RemindAt.Format("2006-01-02 15:04"))
} else {
    fmt.Println("リマインド時刻は未設定")
}

time.Time部分はポインタにすることでnil(他の言語で言うところのNULL)にすることで分岐ができるようにしました。 これはRustでは以下のようなmatch分岐と捉えることができます。

struct Reminder {
    id: i64,
    title: String,
    remind_at: Option<OffsetDateTime>, // Some(...) か None
    done: bool,
    created_at: OffsetDateTime,
}

// 使う側
match &r.remind_at {
    Some(t) => println!("リマインド時刻: {}", t),
    None => println!("リマインド時刻は未設定"),
}

ポインタの存在

前の部分でも出てきましたが、Goではポインタを使用します。 自分がRustを始めた理由の一つに低レイヤを触るならCかRust、でもCはポインタがわからないだったのですが、 結局ポインタに触れなければいけないようです。

とは思っていたのですが、Rustの所有権あたりはポインタを安全に使えるようにしているシステムっぽく、割と頭には入ってきました。

まずポインタについて整理しておくと、値そのものではなく、その変数が格納されているメモリアドレスを保持する変数となります。 C言語ではこのメモリアドレス自体をいじることができるため、自分はかなり複雑に感じていたのですが(ここがC言語のいいとこではあるとは思います)、Goではそのようなことはできず、 単純にメモリアドレスを示すものと言っても良さそうです。C言語のように関数に配列を渡すときは先頭ポインタといったこともありません。

Rustではこれらと似た概念に参照がありました。 &でアドレスを取って*でデリファレンスする記法もそっくりで、 基本的な使い方はGoのポインタとほぼ同じ感覚で書けます。Rustで参照に慣れていたおかげで、Goのポインタも身構えずに入れました。

ただ、見た目は似ていても安全性の担保の仕方はかなり違うとも感じます。 Rustの参照はコンパイル時に「同時に書き換え可能な参照は1つだけ」「絶対にnullにならない」といったルールが強制されるのに対して、 Goのポインタはこうしたチェックがなく、nilデリファレンスやデータ競合はプログラマの注意に委ねられています。

CGoRust
ポインタ演算できるできないできない
メモリ解放手動GC所有権で自動
nil参照セグフォpanicそもそも存在しない

まとめると、Goのポインタは使い勝手はRustの参照に近く、Rust経験者ならすんなり書ける印象です。 違いは安全性の仕組みで、Rustがコンパイル時に事故を止めるのに対し、Goは「シンプルさを優先してGCとプログラマに委ねる」設計になっています。 Cで感じていた「ポインタが怖い」というイメージは、Goではそこまで気にせず書けそうです。

まとめ

何かしらのプログラミング言語が書けるようになっていれば、読めるレベルくらいならGoは割とすんなり入ってくるように感じました。 まあまだまだ知らないことだらけではあるので色々触ってみて慣れていこうと思います。

とりあえず今のリマインダーはAIに書かせてそのコードを読んで分からないところを聞く、実際に少し書き換えてみる。 といった流れで作っているので、これが作り終わったらAIなしで何かしら作ってみて馴染ませようとは思っています。

Tags:

programming

Rust

CLI

Go