32
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🐘 ぺちぱー が Rust を書くようになって うそん てなった話 🦀

32
Last updated at Posted at 2025-12-16

これは5年間 PHP (主に Laravel) で Web 系の API を書いてきたエンジニアが、
転職をきっかけに Rust で API を実装することになって感じた PHP とのギャップ についての話です。

  • Web API という文脈は同じ
  • PHP (Laravel) はそれなりに書いてきた
  • Rust は完全に初心者からスタート

そんな立場から見えた「言語やフレームワークの思想の違い」を共有できればと思います。


最初に

なにゆえ PHP から Rust ?

これは長らく動的型付け言語 のみ で生きてきて、
そろそろ静的型付け言語も触りたい!と思ったのがきっかけです。
その中でも Rust にしたのはなんでしょうね。ロマンです。

さて。
これまでの5年間は、主に Laravel を使って以下のような思想で実装していました。

  • Eloquent ORM を使った API 実装
  • Laravel 推奨のテーブル設計
  • strict モードを使用し、出来うる限り型安全性を担保
  • 可読性・保守性・変更容易性を優先した実装方針
    (多少のパフォーマンスより、読みやすさを取る)

Laravel は、

  • Web アプリケーションを書くための前提が揃っている
  • 「普通に書けば普通に動く」
  • 運用まで含めた開発体験がかなり洗練されている

という印象が強いフレームワークです。

一方、今の会社では Rust を使った API 開発が行われており、
入社後すぐに Rust で API を書くことになりました。

当然、最初にぶつかった壁は言うまでもありませんね。

コンパイルが通らない......;;


型が "設計を言語化するもの" になっている

Rust を触り始めてまず感じたのは、
型が単なる注釈ではなく、設計そのものを表現する役割を持っている という点でした。

PHP でも型と例外はそれなりに意識していた

前提として、PHP 時代も「動けばいい」で型を蔑ろにしたり、
テキトーに実装していたつもりはありません。

/**
 * nullable にする場合
 */
public function getUser(int $id): ?User
{
    return User::find($id);
}

/**
 * ここでスローさせる場合
 * @throws ModelNotFoundException
 */
public function getUserOrFail(int $id): User
{
    return User::findOrFail($id);
}
  • 引数型・return 型は必ず定義
  • 予期する exception はできるだけ PHPDoc に記載
  • できるだけ strict mode での開発

ただし、一見すると PHP にしてはかなり堅そうに見える実装でも、

  • 実行時までエラーに気づけない可能性がある
  • チーム全員が同じ粒度で型を書いてくれる保証はない
    (strict mode にしない、型を書かない、mixed を多用するなど)

といった不安は、どうしても残ります。
(lint ルールをガチガチに設定しておくとある程度マシになるかもですが...)

Rust では厳格さを強制される

Rust の場合、同じような処理は次のようになります。

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "users::Entity")]
pub struct User {
    pub id: UserId,
    pub name: String,
}

impl User {
    pub async fn get(id: UserId, db: &DbConn) -> Result<Option<Self>, DbErr> {
        users::Entity::find()
            .filter(users::Column::Id.eq(id))
            .into_partial_model()
            .one(db)
            .await
    }
}

これを見たとき、まず頭に浮かんだのは

設計書として見るなら、こっちの方が分かりやすいかもしれん

その理由としては、

  • try catch のように「どこでハンドリングされるか分からない」構造ではない
  • Result 型によって、呼び出し側にハンドリングを強制できる
  • どんなエラーが返りうるかを return 型から読み取れる

あたりが大きいです。

今まで try catch 信者でしたが
(というより、それしか知らなかっただけ)、
Rust の Result 型は個人的にかなり使い勝手が良く感じました。

  • 型を書く行為そのものが設計整理になる
  • 書きながら「このケース考えてなかったな」と気づける
  • 後から仕様を読み解くコストが減る
  • rust-analyzer が優秀で、コンパイル時に大抵のエラーは潰せる

というメリットがありそうだと感じています。

Laravel では
「とりあえずあとでハンドリングを考える」
という進め方をすることもありましたが、

Rust では 書くのと同時にハンドリングを確定させていく 感覚があります。

型と一緒に設計を組み立てていく感じで、
頭の中がかなり整理しやすいと感じました。


ORM の “柔軟性” は… さびしいか?

一方で、Rust で API を書いていて 明確に気に食わなかったポイント もあります。
それが ORM、特に論理削除の扱い です。

Laravel ORM の論理削除はスーパーフレンドリー

Laravel の ORM である Eloquent では、
論理削除はほぼ前提機能として標準装備されています。

class User extends Model
{
    use SoftDeletes;
}

$users = User::all(); // 論理削除は自動的に除外される
$users = User::onlyTrashed()->all(); // 論理削除されたレコードのみ
$users = User::withTrashed()->all(); // 論理削除されたレコードも含む
  • 取得系は常に安全側 (有効データのみ) に倒れる
  • 削除済みデータを扱うには明示的な操作が必要
  • うっかり事故が起きにくい

これは単なる便利機能ではなく、
運用を見据えた設計思想 だと思っています。

SeaORM で論理削除を扱ったときの罠

Rust + SeaORM では、論理削除は完全に自前対応になります。

let users = users::Entity::find()
    .filter(users::Column::DeletedAt.is_null())
    .all(&db)
    .await?;

これを見た瞬間、率直に思いました。

論理削除運用、ヤバそう

  • 条件を付け忘れてもコンパイルは通る
  • レビューでも見落としやすい
  • 実行時エラーにもならない

Rust は型システムで多くのミスを防げる一方で、
ORM における「有効データの扱い」は人間の注意力に委ねられている 印象があります。

"思想の違い" では片付けづらい不親切さ

ここは正直、
「Rust はこういう思想だから」で片付けるのは難しいと感じました。

  • ORM として論理削除のサポートが薄い
  • 安全側に倒すためのガードレールが用意されていない
  • チーム運用を考えると、かなり怖い

正直、Web アプリケーションとして実装するなら
必須レベルの機能では? と思っていた分、厳しさを感じました。

実際、チーム内でもこの問題について議論しましたし、
「ないなら自前で作るしかないか……」と考えたこともあります。

現状では、すべてのクエリビルド時に
手動で論理削除フラグの filter を書いています。つらい。

Laravel 時代には深く考えずに済んでいた設計判断が、
Rust では必要になってしまいました。


PHP と Rust では "どこで守ってくれるか" が違う

ここまで振り返ってみて感じたのは、

  • PHP + Laravel
    • フレームワークが多くを肩代わりしてくれる
    • Web 業界で長年使われてきただけあり、開発体験が良い
  • Rust
    • 言語と型が強力なガードレールになる
    • その分、周辺ツールはまだまだ素朴で成長段階にある
      (とはいえ現時点でも企業で本運用できてるくらいには整っている)

という違いです。

Rust は「厳しい言語」と言われがちですが、
少なくとも型に関しては 納得感のある厳しさ だと感じました。

一方で、Laravel の開発体験に慣れているほど、
それなりの覚悟は必要です。
(コンパイル時間も割と辛い)


まとめ

PHP から Rust に移行して感じたギャップをまとめると、

  1. 型は設計をそのまま表現できる強力な道具
    • 書くこと自体が仕様整理になる
    • 型が厳格でコンパイルも厳しいのでバグ率が減る
  2. ORM、とくに論理削除運用は要注意
    • SeaORM は柔軟だが不親切
    • Laravel の「当たり前」は存在しない

Rust で API を書くこと自体は、とても楽しいです。
ただし、Laravel 的な感覚のまま Web 開発を進めると、
思わぬところで足元をすくわれるかもしれません。


32
11
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?