Gemini API の公式 SDK 一覧を眺めると、Python・JavaScript・Go・Java・Kotlin・Swift・Dart と並んでいて、Rust だけがありません。ゼロコスト抽象化とメモリ安全性を求めてバックエンドを Rust で組んでいる方には、少し寂しい並びです。
ただ、これは実質的な障害になりません。Gemini API は素直な RESTful エンドポイントを公開しているので、HTTP クライアントクレートの reqwest があれば数十行で繋がります。扱うのは次の5点です。
- Cargo プロジェクトのセットアップと必要なクレートの追加
- Gemini API へのテキスト生成リクエスト(非同期)
- Server-Sent Events(SSE)を使ったストリーミングレスポンスの受信
- Base64 エンコードした画像を送るマルチモーダル入力
- よくあるエラーのトラブルシューティング
対象読者は Rust の基礎文法を理解している方 で、AI API の利用は初めてでも問題ありません。
前提条件・環境準備
必要なもの
- Rust(1.75 以降推奨):
rustup update stable - Gemini API キー: Google AI Studio で無料取得可能
- 安定したインターネット接続
API キーの取得
Google AI Studio にアクセスし、Google アカウントでサインイン後、「Get API key」から新しいキーを作成してください。取得したキーは環境変数として管理します。
export GEMINI_API_KEY="YOUR_GEMINI_API_KEY"セキュリティ注意: APIキーをソースコードに直接書き込まないでください。
dotenvクレートや環境変数を活用しましょう。
Cargo プロジェクトの作成
cargo new gemini-rust-demo
cd gemini-rust-demoCargo.toml に以下の依存関係を追加します。
[dependencies]
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
dotenvy = "0.15"
base64 = "0.22"
futures-util = "0.3"テキスト生成 — 基本的な API 呼び出し
まずは最もシンプルなテキスト生成から始めましょう。Gemini API の /generateContent エンドポイントに JSON ペイロードを送信します。
use reqwest::Client;
use serde_json::{json, Value};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 環境変数から API キーを取得
let api_key = env::var("GEMINI_API_KEY")
.expect("GEMINI_API_KEY が設定されていません");
let client = Client::new();
// リクエストボディを構築
let body = json!({
"contents": [{
"parts": [{
"text": "Rust プログラミング言語の特徴を3つ挙げて、それぞれ簡潔に説明してください。"
}]
}]
});
// Gemini 2.5 Flash にリクエストを送信
let url = format!(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={}",
api_key
);
let response = client
.post(&url)
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?;
let json: Value = response.json().await?;
// レスポンスからテキストを取り出す
let text = &json["candidates"][0]["content"]["parts"][0]["text"];
println!("Gemini の回答:\n{}", text.as_str().unwrap_or("回答を取得できませんでした"));
Ok(())
}期待する出力例:
Gemini の回答:
Rust プログラミング言語の3つの主な特徴:
1. **メモリ安全性**: ガベージコレクタなしにメモリ安全性を保証します。...
2. **高いパフォーマンス**: C/C++ に匹敵する実行速度を実現します。...
3. **ゼロコスト抽象化**: 高レベルの抽象化が実行時コストを生みません。...
システムインストラクションの追加
Gemini API では System Instructions を設定することで、モデルの役割や応答スタイルを定義できます。
let body = json!({
"system_instruction": {
"parts": [{
"text": "あなたは Rust の専門家です。技術的な正確さを保ちながら、初心者にもわかりやすく説明してください。"
}]
},
"contents": [{
"role": "user",
"parts": [{
"text": "所有権(Ownership)とは何ですか?"
}]
}]
});ストリーミングレスポンスの実装
長い応答を待たずにリアルタイムで受信するには、streamGenerateContent エンドポイントを使います。Rust では futures-util を使って SSE ストリームを処理できます。
use futures_util::StreamExt;
use reqwest::Client;
use serde_json::{json, Value};
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("GEMINI_API_KEY")?;
let client = Client::new();
let body = json!({
"contents": [{
"parts": [{
"text": "Rust の非同期プログラミングについて詳しく説明してください。"
}]
}]
});
let url = format!(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?key={}&alt=sse",
api_key
);
let mut stream = client
.post(&url)
.header("Content-Type", "application/json")
.json(&body)
.send()
.await?
.bytes_stream();
// ストリームをチャンクごとに処理
while let Some(chunk) = stream.next().await {
let bytes = chunk?;
let text = String::from_utf8_lossy(&bytes);
// "data: " プレフィックスを除去して JSON を解析
for line in text.lines() {
if let Some(json_str) = line.strip_prefix("data: ") {
if json_str.trim() == "[DONE]" {
break;
}
if let Ok(json) = serde_json::from_str::<Value>(json_str) {
if let Some(part_text) = json["candidates"][0]["content"]["parts"][0]["text"].as_str() {
print!("{}", part_text); // リアルタイム出力
use std::io::Write;
std::io::stdout().flush()?;
}
}
}
}
}
println!(); // 改行
Ok(())
}マルチモーダル入力 — 画像の解析
Gemini のマルチモーダル能力を活かして、画像をテキストと組み合わせて送信できます。画像ファイルを Base64 エンコードして inlineData として渡します。
use base64::{engine::general_purpose, Engine as _};
use reqwest::Client;
use serde_json::{json, Value};
use std::{env, fs};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("GEMINI_API_KEY")?;
let client = Client::new();
// 画像ファイルを読み込んで Base64 エンコード
let image_bytes = fs::read("sample.jpg")?;
let base64_image = general_purpose::STANDARD.encode(&image_bytes);
let body = json!({
"contents": [{
"parts": [
{
"inline_data": {
"mime_type": "image/jpeg",
"data": base64_image
}
},
{
"text": "この画像に写っているものを詳しく説明してください。"
}
]
}]
});
let url = format!(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={}",
api_key
);
let response = client.post(&url).json(&body).send().await?;
let json: Value = response.json().await?;
println!(
"{}",
json["candidates"][0]["content"]["parts"][0]["text"]
.as_str()
.unwrap_or("解析結果を取得できませんでした")
);
Ok(())
}マルチターン対話(チャット)の実装
会話の文脈を保持するには、過去の contents 配列を累積して送り続けます。
use reqwest::Client;
use serde_json::{json, Value};
use std::env;
use std::io::{self, BufRead, Write};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("GEMINI_API_KEY")?;
let client = Client::new();
let url = format!(
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={}",
api_key
);
// 会話履歴を Vec で管理
let mut conversation: Vec<Value> = Vec::new();
let stdin = io::stdin();
println!("Gemini とチャットを開始します('quit' で終了)");
for line in stdin.lock().lines() {
let user_input = line?;
if user_input.trim() == "quit" {
break;
}
// ユーザーメッセージを履歴に追加
conversation.push(json!({
"role": "user",
"parts": [{ "text": user_input }]
}));
let body = json!({ "contents": conversation });
let response = client.post(&url).json(&body).send().await?;
let json: Value = response.json().await?;
if let Some(reply) = json["candidates"][0]["content"]["parts"][0]["text"].as_str() {
println!("Gemini: {}", reply);
// アシスタントの返答を履歴に追加
conversation.push(json!({
"role": "model",
"parts": [{ "text": reply }]
}));
}
print!("You: ");
io::stdout().flush()?;
}
Ok(())
}よくあるエラーと対処法
Rust で Gemini API を使う際に遭遇しやすいエラーをまとめます。
400 Bad Request
JSON の構造が正しくない場合に発生します。serde_json::to_string_pretty(&body) でリクエストボディを確認しましょう。
403 API_KEY_INVALID
API キーが無効または環境変数が設定されていない場合です。echo $GEMINI_API_KEY で確認してください。
429 RESOURCE_EXHAUSTED
レート制限に達しています。無料ティアでは 1 分あたりのリクエスト数に上限があります。tokio::time::sleep でリトライ間隔を設けましょう。
use tokio::time::{sleep, Duration};
// 指数バックオフによるリトライ
for attempt in 0..3 {
let response = client.post(&url).json(&body).send().await?;
if response.status() == 429 {
let wait_secs = 2_u64.pow(attempt);
eprintln!("レート制限。{}秒後にリトライします...", wait_secs);
sleep(Duration::from_secs(wait_secs)).await;
continue;
}
// 成功処理...
break;
}reqwest の TLS エラー
Cargo.toml で reqwest の rustls-tls フィーチャーが必要な環境もあります。
reqwest = { version = "0.12", features = ["json", "stream", "rustls-tls"] }さらに詳しい実装テクニック(Function Calling・ツール統合・プロダクション設計)については、Gemini API Function Calling完全ガイド で解説しています。
まとめ
ここではRust から Gemini API を呼び出す方法を基礎から解説しました。
- reqwest + serde_json の組み合わせで REST API を簡単に呼び出せる
streamGenerateContentエンドポイントと SSE でリアルタイムなストリーミングを実現できるinline_dataを使えばマルチモーダル(画像解析)も対応可能- 会話履歴を Vec で管理することでマルチターン対話も実装できる
Rust の安全性とパフォーマンスを活かしながら Gemini の AI 能力を組み合わせることで、堅牢な AI バックエンドサービスを構築できます。次のステップとして、Axum や Actix-web などの Web フレームワークと組み合わせた REST API サーバーの構築に挑戦してみてください。