はじめに — なぜ今 “ARM64 × Rust × Python” がハマりポイントになるのか
Apple Silicon 対応 Mac の登場以降、**「Rust(ネイティブ速度) × Python(開発速度)を PyO3 でつなぐ」**というハイブリッド構成は、パフォーマンスと生産性の両取りを狙う開発者にとって極めて魅力的な選択肢になりました。しかし実際に手を動かすと、アーキテクチャの食い違い・ツールチェーンの混在・クロスコンパイル特有の罠が次々に浮き彫りになります。ここではまず「なぜハマるのか」を整理し、後続パートで具体的な解決フローを示します。
M シリーズ Mac とクロスアーキの壁
- Rosetta 2 とネイティブ arm64 の二重環境
- Homebrew は Intel バイナリ(/usr/local)と Apple Silicon バイナリ(/opt/homebrew)を “両立” できる。
- 端末を Rosetta で開くと Python も Rust も x86_64 側を参照しがち。
- その結果、
cargo build
が x86_64 のrustc
を呼び、Python venv は arm64、という アーキテクチャミスマッチ が発生。
- 標準ライブラリと sysroot の氷山
- Rust はターゲットごとに
libcore
,libstd
を持つ。 - x86_64 用の
rustc
が arm64 のrust-std
を認識できず、cannot find crate for 'core'
が連発。 - 表面的には「型が見つからない」ように見えて、実際は sysroot の参照先が異なるだけ。
- Rust はターゲットごとに
- brew 版 Rust vs. rustup 版 Rust
brew install rust
とrustup toolchain install
が共存すると PATH 優先度で古い brew バイナリが勝つ。rustup default
を切り替えても maturin / cargo が brew の/usr/local/bin/rustc
を呼び続ける。
PyO3 を添えたハイブリッド戦略の魅力と落とし穴
魅力 | 落とし穴 |
---|---|
✅ 生産性 × 速度 アルゴリズムのクリティカルパスだけ Rust で書き、Python から直接呼べる。 | ⚠️ ビルドが二段階 Rust 側は cdylib、Python 側は wheel。ツールチェーンとアーキがズレると即死。 |
✅ ワンバイナリ配布maturin build --release --target universal2-apple-darwin で Universal2 Wheel を生成できる。 | ⚠️ SIMD・FFI の罠std::arch::x86_64 は arm64 では存在せず、条件付きコンパイル必須。 |
✅ Python ←→ Rust の型変換自動化 PyO3 が Vec<f64> ↔︎ list などを安全にブリッジ。 | ⚠️ IDE 警告の洪水 rust-analyzer は PyO3 proc-macro を完全展開できず “赤線” が消えにくい。 |
ポイント
- パフォーマンス向上は劇的だが、ツールチェーン管理を誤ると「動くまでが長い」。
- 「ビルドが通らない=コードが悪い」と思いがちだが、8割は環境不整合。

次節では、私が実際に踏んだエラーのタイムラインを追いながら、どこで環境の“ねじれ”が生じ、どうやって一つずつほどいたかを具体的に解説します。
症状のタイムライン ― 発生したエラーと一次切り分け
ここでは、Apple Silicon 上で Rust × PyO3 をビルドする過程で実際に遭遇したエラーを “発生順” に追いながら、最初の切り分けポイント と “ハマったらまず疑うべき箇所” を整理します。
1️⃣ cannot find crate for 'core'
から始まる負の連鎖
発生日 | 画面に出たメッセージ | 表面的な意味 | 実際の原因 | 最初に打つべきコマンド |
---|---|---|---|---|
Day 1 | error[E0463]: cannot find crate for 'core' | Rust の標準ライブラリが見つからない | aarch64 用 sysroot が未インストール | rustup target add aarch64-apple-darwin |
Day 1 | error[E0463]: cannot find crate for 'std' | core 消えると当然 std も消える | 上と同一 | 同上 |
Day 1 | 依存 crate すべてでcannot find type 'Option' in this scope | libcore が読めないので基本型が解決できない | 上と同一 | 同上 |
観察ポイント
- “core / std が丸ごと見つからない” エラーは 99 % = ターゲット未インストール or 壊れた sysroot。
- コードの書き方を疑う前に、
rustup target list --installed
を見る。
2️⃣ “エラー再発” → アーキテクチャ不一致が露呈
sysroot を入れ直しても maturin だけが再び同じエラーを吐いた場合、次に疑うのは Rust/Python/brew のアーキテクチャが混在しているかどうか。
チェック項目 | 期待値 (Apple Silicon ネイティブ開発) | NGパターン | 確認コマンド |
---|---|---|---|
rustc host | aarch64-apple-darwin | x86_64-apple-darwin | `rustc -vV |
Python 仮想環境 | arm64 | x86_64 (Rosetta) | python -c "import platform; print(platform.machine())" |
which cargo rustc | ~/.cargo/bin/* | /usr/local/bin/* (brew) | which cargo rustc |
一次切り分けルール
- ホストアーキがズレていたら →
rustup default stable-aarch64-apple-darwin
。- brew 版 Rust が PATH 先頭にあったら →
brew uninstall rust
orbrew unlink rust
。- Python が Rosetta → 仮想環境を
arch -arm64 /usr/bin/python3 -m venv .venv
で作り直す。
3️⃣ “標準ライブラリ未検出” vs. “アーキテクチャ不一致” を 30 秒で見極める
現象 | ほぼ sysroot 未インストール | ほぼアーキテクチャ不一致 |
---|---|---|
rustc 単体ビルド (cargo build ではなく) で core が見つからない | 💥 出る | ✅ 通る |
rustup target list --installed に aarch64-apple-darwin がない | 💥 | ✅ |
which rustc が ~/.cargo/bin/rustc | ✅ | 💥 brew をつかむ |
`rustc -Vv | host =と Python platform.machine()` が一致 | ✅ |
ワンライナー診断
rustc -vV | grep host; python - <<'PY' import platform; print("Python:", platform.machine()) PY
両方がarm64
/aarch64
ならアーキ OK。片方でもx86_64
が混じれば “環境ねじれ” 確定。
✋ 切り分けメモ
- 同じエラーが何度も蘇るときは “キャッシュ汚れ”より 別アーキのツールが潜伏しているケースがほとんど。
- 迷ったら
which cargo rustc
で パスを絶対確認。ディレクトリを見れば brew か rustup か一目瞭然。 brew uninstall rust
は勇気が要るが、ハイブリッド開発では rustup 一本化が最もトラブルが少ない。

次のセクションでは、決定打として行った環境修正と、SIMD・PyO3 API 移行などコード側の対策を時系列で解説します。
決定打① : Rust toolchain と Python 環境のアーキテクチャ統一
Apple Silicon 上で “Rust × PyO3” を動かす上で、もっとも効果が大きかった対策が 「ツールチェーンと Python 環境を同じアーキテクチャ(arm64)にそろえる」 ことでした。ここでは、実際に行ったコマンドとチェックポイントを 最小手順 でまとめます。
1. Rust 側:arm64 ターゲットを入れてデフォルト化
# arm64 用標準ライブラリをインストール rustup target add aarch64-apple-darwin # M シリーズ Mac なら stable-arm64 をデフォルトに rustup toolchain install stable-aarch64-apple-darwin rustup default stable-aarch64-apple-darwin
ポイント
rustup target list --installed
にaarch64-apple-darwin
が見えれば OK。- 以後
cargo build
は自動で arm64 用 sysroot を使う。
2. brew 版 Rust を退役し、PATH を再構成
brew 経由で入れた旧バージョン(Intel ビルド)が PATH の先頭にいると、rustc
が混在して再発します。一度アンインストールして rustup 一本に統一するのが最短です。
# (安全策:依存確認してからでも可) brew uninstall rust # 端末を開き直して PATH を確認 echo $PATH which cargo rustc
which
の結果が /Users/xxx/.cargo/bin/cargo
と .cargo/bin/rustc
になっていれば完了。
もし brew を残したい場合は brew unlink rust
でバイナリだけ外すか、export PATH="$HOME/.cargo/bin:$PATH"
をシェル RC に追記して優先度を逆転させます。
3. Python 仮想環境も arm64 で作り直す
Rosetta 端末で venv を作ってしまうと Python 側が x86_64 になります。必ずネイティブ arm64 ターミナルで:
arch -arm64 /usr/bin/python3 -m venv .venv source .venv/bin/activate python -c "import platform; print(platform.machine())" # → arm64
4. ビルド時は “rustc が誰か” を常に確認
# maturin の前に確認ワンライナー which cargo rustc && cargo --version && rustc -vV | head -n 5
which
が.cargo/bin
を指しているかhost: aarch64-apple-darwin
と表示されるか
これが チェックリスト最上位。ズレていれば環境変数や PATH を見直すだけで、多くのビルドエラーは消えます。
5. ✔️ 動作確認(決定打のチェック)
# Rust クレートだけビルド cargo build --workspace --exclude mm_bot_rust # PyO3 拡張をビルド&インストール cd src/bindings RUSTUP_TOOLCHAIN=stable-aarch64-apple-darwin \ maturin develop --target aarch64-apple-darwin
cannot find crate for 'core'
が再発しないrunning: rustc ... --crate-name
のパスが .cargo/binFinished dev [unoptimized + debuginfo]
が表示される
上記が揃えば 環境統一は成功、以降はコード起因の警告・最適化だけに集中できます。
決定打② : 条件付きコンパイルで SIMD/アーキ別コードを生かす
「ビルドは通った、でも x86 SIMD 命令が arm64 ではシンボル未定義」──
ハイブリッド構成で高性能を狙うと、ここで再び落とし穴にハマります。解決策はシンプルで強力:条件付きコンパイルでアーキ別ロジックを切り替える。
1. #[cfg(target_arch)]
マクロで x86_64 専用命令を安全ラップ
// src/simd.rs #[cfg(target_arch = "x86_64")] mod x86_simd { use std::arch::x86_64::*; #[inline] pub unsafe fn dot_f32(a: __m256, b: __m256) -> f32 { let mul = _mm256_mul_ps(a, b); // horizontal add → [a0*b0 + … + a3*b3, …] let hadd = _mm256_hadd_ps(mul, mul); // extract lower 128 bits let low = _mm256_castps256_ps128(hadd); let high = _mm256_extractf128_ps(hadd, 1); _mm_cvtss_f32(_mm_add_ss(low, high)) } } #[cfg(target_arch = "aarch64")] mod x86_simd { // 同名 API をダミー実装(panic で止める) pub unsafe fn dot_f32(_a: (), _b: ()) -> f32 { unreachable!("x86 SIMD called on arm64") } } pub use x86_simd::dot_f32;
ポイント
#[cfg(target_arch = "x86_64")]
で x86 AVX 実装だけをコンパイル。- 同じ関数名を aarch64 モジュールにも用意し、コンパイルは通すが実行すると panic で即気づく。
- 呼び出し側は共通 API を
use
するだけでアーキ依存を意識しない。
2. ARM64 実装を “簡易 but 高速” に置き換えるコツ
Apple Silicon(M1〜M4)は Neon SIMD が標準搭載ですが、AVX 相当の高位 API がまだ安定していません。そこで:
アプローチ | 実装難易度 | 性能 | メリット |
---|---|---|---|
a. Neon ASMstd::arch::aarch64::* を手書き | ★★★★☆ | ◎ | 本気ベンチ向け。保守コスト高 |
b. wide / simdeez など汎用 SIMD クレート | ★★★☆☆ | ○ | アーキ自動切り替え。依存増 |
c. Fallback (scalar) 実装 | ★★☆☆☆ | △〜○ | コード最短。Hotspot を detour で差し替え可 |
例:ベースラインを scalar で書きつつ、特徴量ごとに Neon を差し替え
#[cfg(target_arch = "aarch64")] #[inline] fn dot_f32_scalar(a: &[f32; 8], b: &[f32; 8]) -> f32 { a.iter().zip(b).map(|(x, y)| x * y).sum() } #[cfg(target_arch = "aarch64")] #[inline] unsafe fn dot_f32_neon(a: *const f32, b: *const f32) -> f32 { use std::arch::aarch64::*; let va = vld1q_f32(a); let vb = vld1q_f32(b); // … 実装略(Neon 命令で乗算 + 水平加算) // 最終値を返す 0.0 } #[cfg(target_arch = "aarch64")] pub fn dot_f32_arm(a: &[f32; 8], b: &[f32; 8]) -> f32 { // 開発初期は安全なスカラー版 let base = dot_f32_scalar(a, b); // プロファイル結果が十分なら Neon 版に置き換え // unsafe { dot_f32_neon(a.as_ptr(), b.as_ptr()) } base }
コツ
- まずはスカラー版でユニットテストを通す → 動作保証が先。
cargo bench
+cargo criterion
などで Hotspot を測定。- 性能が足りなければ Neon 版の関数を
unsafe
ブロックで切り替え。
ベンチで差が 1.5〜2× 程度ならスカラー版でも十分な場合が多いので、「先に機能を優先 → ボトルネックだけ Neon」を意識すると実装コストを抑えられます。
3. 条件付きコンパイルのチェックリスト
- 同じシンボル名を各モジュールで定義 → 呼び出し側の if/else 分岐を消せる。
cfg_if::cfg_if!
マクロを使うと 複数アーキを 1 カ所で切り替えられ、記述ミスを防げる。- 新アーキ追加時は
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
のように包括。 cargo check --all-targets
で unused 警告が出ても無害。CI では--target aarch64-apple-darwin
と--target x86_64-apple-darwin
の両方を回す。
この“アーキ別ラッパー戦略”で、
- x86 環境では AVX/AVX2 の高速経路、
- Apple Siliconでは Neon あるいは妥協スカラー経路、
の両立が容易になります。次のセクションでは、PyO3 0.21 へアップグレードしても壊れない API ラップと wheel 配布戦略を詳細に紹介します。
決定打③ : PyO3 0.21 へのアップグレードと API 移行
Rust/Python 両方のアーキがそろっても、PyO3 のバージョン差異が残っていると再びビルドが止まります。ここでは 0.20 → 0.21 で変わったポイントと、最短で移行する手順をまとめます。
1. Cargo.toml
を最新形にそろえる
[dependencies] pyo3 = { version = "0.21", features = ["extension-module"] } pyo3-build-config = "0.21" # ← NEW [build-dependencies] pyo3-build-config = "0.21" # build.rs で自動検出する場合
変更点
pyo3-build-config
を明示
0.21 から “Python 検出ロジック” が独立クレートになったため、
build.rs でpyo3_build_config::get()
を呼ぶ場合は build-deps にも追加。features = ["extension-module"]
は従来どおり。abi3-py38
などを併用する場合はここに追加する。
2. 新しい PyModule
バインドシグネチャ
0.21 で Bound<'_, PyModule>
が導入され、マクロ側も自動変換されます。
基本パターンは関数の第 2 引数だけ直せば OK。
// before (0.20) #[pymodule] fn mm_bot_rust(_py: Python, m: &PyModule) -> PyResult<()> { // ... Ok(()) } // after (0.21) #[pymodule] fn mm_bot_rust(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { // m.add_function(wrap_pyfunction!(foo, m)?) なども同じ Ok(()) }
なぜ変わった?
Bound<'py, T>
は GIL 保持状態を型で保証 するラッパ。&PyModule
→&Bound<'_, PyModule>
に置き換わるだけでコードはほぼ同じ。
3. #[pyfunction]
ラッパの細かな変更
wrap_pyfunction!
の第 2 引数 はm
の型が変わっても互換。#[pymethods]
内部でname = "foo"
のような属性指定が増えても問題なし。- deprecated 警告が出たら、
pyo3
CHANGELOG にある ”Renamed to …” をそのまま反映すれば通る。
4. ビルドフローの更新 (例:build.rs
)
// build.rs fn main() { // PyO3 が Python を自動検出 pyo3_build_config::configure().unwrap(); println!("cargo:rerun-if-env-changed=PYTHON_SYS_EXECUTABLE"); }
ポイント
- 0.21 から
pyo3_build_config::configure()
一行で
include path / lib path / config flags を export してくれる。build.rs
が不要なら ファイルごと削除しても構わない(簡潔に保つ)。
5. 移行を壊さないための CI ワンステップ
- name: Check PyO3 upgrade run: | cargo check --workspace maturin build --release --target aarch64-apple-darwin
cargo check
で Rust 側 API の破綻を即検知。maturin build
がFinished release
なら Python バインドも OK。
6. つまずきやすい罠メモ
エラー | 見込み原因 | 対処 |
---|---|---|
error[E0432]: unresolved import pyo3::prelude::PyModule | use pyo3::prelude::*; が古いファイルに残存 | cargo fix --edition で自動修正/手動で Bound 型へ |
BuildConfig not found | [build-dependencies] pyo3-build-config 欠落 | Cargo.toml に追記 |
symbol not found: PyInit_mm_bot_rust (実行時) | #[pymodule] fn 名と [lib] name = が不一致 | 両方を同一 snake_case に揃える |
📝 まとめ
pyo3 = "0.21"
+pyo3-build-config
明示がアップグレードの核心。- 型シグネチャ変更は
&PyModule
→&Bound<'_, PyModule>
一カ所だけで済む。 - CI で
cargo check
+maturin build
の 2 段テストを回せば 破壊的変更の混入を即捕捉。
次回 PyO3 0.22 以降が来ても、このパターンを踏襲すれば最小手間で追随できます。
ビルド成功までの最終ループ ― maturin 開発ワークフロー
環境を一本化し、PyO3 0.21 に移行したら、日々の開発では 「Rust コードを修正 → Python 拡張を再ビルド → テスト」 を気持ちよく回せるワークフローが必要です。ポイントは maturin をワンコマンド化 して “wheel の種類” を作業内容に応じて選べるようにしておくこと。
1. RUSTUP_TOOLCHAIN=… maturin develop
を Makefile に落とし込む
# Makefile(ルート直下などに配置) PY_TARGET ?= $(shell rustc -vV | grep host | awk '{print $$2}') .PHONY: bindings bindings: @echo "🛠 Building PyO3 bindings for $(PY_TARGET)…" cd src/bindings && \ RUSTUP_TOOLCHAIN=stable-$(PY_TARGET) \ maturin develop --target $(PY_TARGET) --quiet
使い方
# 端末 (arm64) で make bindings
メリット
- ホストアーキを自動検出 (
rustc -vV
):Rosetta 端末でも x86_64 wheel が作れる。 --quiet
でログを最小化。失敗時はエラーだけを表示。- 依存追加 →
make bindings
→ Python から即インポート、という最短ループが完成。
2. リリース用 wheel:universal2 と abi3 の選択指針
目的 | コマンド例 | 生成物 | 向いているケース |
---|---|---|---|
ローカル開発 | maturin develop --target $(PY_TARGET) | .so を直接 venv にインストール | デバッグ / 単体テスト |
Mac 配布 (両アーキ) | maturin build --release --target universal2-apple-darwin | universal2 wheel(arm64 + x86_64) | pip 配布、CI/CD パイプで 1 ファイル納品 |
長寿 wheel (abi3) | maturin build --release -i python3.8 -F abi3-py38 | 各アーキごとの abi3 wheel | Python 3.8 以降で再ビルド不要。内部 PyO3 も安全に更新可 |
選択の目安
- 社内配布・短期開発 → universal2:最も簡単。サイズは 2 倍になるが macOS 系なら問題なし。
- PyPI 公開・複数プラットフォーム長期サポート → abi3:ホスト別ビルドが増えるが、CPython 更新に追随しやすい。
3. CI への組み込み(GitHub Actions 例)
jobs: build-wheel: runs-on: macos-14 strategy: matrix: target: [universal2-apple-darwin, aarch64-apple-darwin, x86_64-apple-darwin] steps: - uses: actions/checkout@v4 - uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: off sccache: true args: --release
要点
- matrix で universal2 と単アーキ wheel を並列ビルド → アップロード。
- sccache を有効にすると依存クレートの再ビルドがほぼゼロ。
4. 失敗しないためのミニチェックリスト
- ビルド前に必ず
cargo check
bashコピーする編集するcargo check --workspace
make bindings
が 5 秒以上かかり始めたらcargo clean -p offending_crate
で汚れを落とす- 依存アップデート後の “無駄フルビルド” を抑制
- wheel の中身を Size チェック bashコピーする編集する
du -h target/wheels/*.whl
- universal2:通常の約 2 ×
- 余分に動的リンクしていないかを把握
これで “コード修正 → make bindings
→ テスト” のループが秒単位で回り、
リリース時は maturin build --target universal2-apple-darwin
だけで公開可能な wheel が手に入ります。

次のセクションでは、このフローを CI と GitHub Releases に接続し、タグを切るだけで自動リリースできるパイプラインを構築する手順を紹介します。
完全解決後の CI / 再発防止チェックリスト
ローカル環境が安定しても、CI が別アーキで実行される・依存が更新されるだけで “沼” は再来します。ここでは 「二度と同じ罠に落ちない」ためのガードレール をまとめます。
1. GitHub Actions で arm64 / x86_64 行列テスト
name: build-and-test on: pull_request: push: branches: [main] jobs: rust-py3: runs-on: ${{ matrix.os }} strategy: matrix: include: - { os: macos-14, target: aarch64-apple-darwin } # Apple Silicon runner - { os: macos-14, target: x86_64-apple-darwin } # Rosetta runner steps: - uses: actions/checkout@v4 # ---------- Rust toolchain ---------- - name: Install Rust ${{ matrix.target }} uses: dtolnay/rust-toolchain@stable with: toolchain: stable targets: ${{ matrix.target }} components: rust-src # ---------- Python ---------- - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.12" architecture: ${{ matrix.target == 'aarch64-apple-darwin' && 'arm64' || 'x64' }} # ---------- Build wheel ---------- - name: Build wheel with maturin uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release manylinux: off # macOS only sccache: true # キャッシュで高速化 # ---------- Rust tests ---------- - name: cargo test run: cargo test --workspace --exclude mm_bot_rust --target ${{ matrix.target }}
ポイント
- 公式 macOS-14 ランナーは arm64 / x86_64 両方に対応。
dtolnay/rust-toolchain
アクションを使い rust-src を含めて pin。- Python も
architecture
を行列に合わせ、実行&wheel インストールが同じアーキになるよう固定。
2. Xcode Command Line Tools & rust-src コンポーネントの固定
依存 | 何が起こると壊れる? | 固定・検証ポイント |
---|---|---|
Xcode CLT | Apple が毎年更新 → LLVM パスが変わると linker not found | CI 開始直後にxcode-select -p を echoしてログ保存バージョンアップ時はランナーイメージを先に確認 |
rust-src | 一部の build-script が build-std を要求 | CI toolchain でrust-src を 必ず component addローカルでも `rustup component list --installed |
Maturin バージョン | PyO3 新機能を追い切れずビルド失敗 | pip install maturin==1.* など minor を pinアップグレードは PR ごとに行列テストが通るか先に確認 |
3. VS Code rust-analyzer の proc-macro 設定
開発体験を守るために、“赤線” を減らしておく。
// .vscode/settings.json { // PyO3 の #[pymodule] / #[pyfunction] 展開を有効化 "rust-analyzer.procMacro.attributes.enable": true, // 未解決 proc-macro 警告を抑制(実ビルドで OK なら無視) "rust-analyzer.diagnostics.disabled": [ "unresolved-proc-macro" ], // arm64 環境では universal binary でなくネイティブ rust-analyzer を使う "rust-analyzer.serverPath": "/Users/$(whoami)/.cargo/bin/rust-analyzer" }
Tips
- VS Code が Rosetta 版になっていると、
rust-analyzer
も x86_64 を掴む。アプリを arm64 版で再インストールして揃える。 .cargo/bin/rust-analyzer
が未インストールなら
rustup component add rust-analyzer
4. ワンページ再発防止チェックリスト(抜粋)
タイミング | チェック | コマンド or ファイル |
---|---|---|
新マシンセットアップ | `rustc -vV | host= aarch64-apple-darwin` |
brew update 直後 | which cargo rustc で /usr/local/bin が先頭に来ていないか | brew uninstall rust / brew unlink rust |
依存 bump (PyO3, maturin) | CI 行列パス、Rust tests、maturin build が緑になる | GitHub Actions |
VS Code 赤線増殖 | proc-macro 展開を有効化 & キャッシュリセット | コマンドパレット → “Rust Analyzer: Reload Workspace” |
✅ まとめ
- 行列テストで「arm64 だけ動く/x86_64 だけ壊れる」を早期検知。
- Xcode CLT・rust-src を toolchain install 時に固定。
- IDE 設定を合わせて“開発は快適、CI は堅牢”を両立。

これで Apple Silicon × PyO3 プロジェクトでも “動く環境を壊さずに進化させる” サイクルが確立できます。
学びとベストプラクティスまとめ
“環境の整合性が 9 割” を身をもって理解した話
ビルドエラーの多くはコードのバグではなく 「ツールチェーン・アーキテクチャ・PATH のねじれ」 に起因していました。
- 標準ライブラリ未検出 → ターゲット未インストール
- 同じエラーが再発 → brew Rust と rustup Rust の競合
- リンク時に謎の未定義シンボル → Python venv のアーキが x86_64
解決の決定打は コード修正 ではなく 環境の一本化。以後は“動かないときはまず環境を疑う”のが合言葉です。
クロスプラットフォーム開発で最初に読むべきチェックリスト
🔍 項目 | arm64 Mac なら OK | コマンド |
---|---|---|
Rust host & target | aarch64-apple-darwin | `rustc -vV |
rust-src / rust-std | インストール済 | `rustup component list --installed |
PATH 優先度 | ~/.cargo/bin が先頭 | which cargo rustc |
Python venv | platform.machine() == 'arm64' | python - <<'PY'\nimport platform; print(platform.machine())\nPY |
brew Rust | アンインストール or unlink | `brew list |
maturin develop | Running \ rustc`が .cargo/bin/rustc` | maturin -vv develop … |
VS Code rust-analyzer | proc-macro 展開 ON | settings.json 編集 |
この表を 1 分で一周して OK なら、ほぼ環境トラブルは排除できます。
未来の自分へ:依存アップデート時にまず確認する 3 行
# 1) どの rustc・cargo を呼んでいる? which cargo rustc # 2) ホストとターゲットが一致している? rustc -vV | grep host # 3) Python venv は正しいアーキ? python -c "import platform, sys; print(platform.machine(), sys.prefix)"
この 3行チェック をパスしていれば、PyO3 や依存クレートのメジャーアップデートでも
「環境が崩れてビルド不能」という事故はほぼ防げます。
まとめ
- 環境の整合性が 9 割 — コードを疑う前にツールチェーンを揃える。
- チェックリストをルーチン化 — 新 PC/CI/アップデート時は 1 分診断。
- 将来の自分への 3 行 —
which
,rustc -vV
,platform.machine()
で真っ先に健康診断。

これさえ守れば、Apple Silicon × Rust × PyO3 の“沼”は再びあなたを飲み込まないはずです。
おわりに — 沼を越えた先のパフォーマンス最適化へ
環境まわりの“沼”を踏破した今、ようやく本題の パフォーマンス追求 に全リソースを割けます。最後に、今後取り組む最適化プランと、同じハマりを回避する仲間を増やすためのお願いで締めくくります。
今後着手するベンチマーク/SIMD 拡張アイデア
フェーズ | 狙い | 具体アクション |
---|---|---|
Phase 1: ベースライン測定 | 最適化前の “現実” を数値化 | - criterion による 関数単位ベンチ- perf / Instruments で ホットスポット特定 |
Phase 2: Neon & AVX2 実装 | 浮動小数点演算を 4〜8× に | - std::arch::aarch64::* で Neon 内積- std::arch::x86_64::* で AVX2/AVX512- Fallback scalar を維持し A/B 計測 |
Phase 3: コード生成自動化 | 手書き SIMD の保守コスト削減 | - wide , simdeez など 汎用 SIMD クレート検証- macro + cfg_if! でアーキ自動切替 |
Phase 4: マルチスレッド & Rayon | スレッド並列で 2〜4× | - rayon::prelude::* を段階的に再導入- キャッシュライン衝突テスト |
Phase 5: Python↔Rust 越境コスト削減 | FFI オーバーヘッドを隠蔽 | - バッチ API で跨ぎ回数を 1/10 に - pyo3::PyBuffer でコピー削減 |
メトリクス指標
- 1 MB/秒あたりの処理レイテンシ
- スループット (ops/sec) vs. CPU 使用率
- Python→Rust→Python 往復回数
これで 環境整備→ビルド安定→性能チューニング への道筋が整いました。
今後は実際のベンチ結果と SIMD 採用のインパクトをレポート予定です。