## Rust クロージャの理解

クロージャは、周囲の環境から変数を捕捉する匿名関数です。  
型安全性を保ちつつ、コールバックやインラインロジックを書きやすくします。

### 主な概念

- **キャプチャモード**  
  - `&T`:不変参照で借用(`Fn`)  
  - `&mut T`:可変参照で借用(`FnMut`)  
  - `T`:所有権を移動(`FnOnce`)

- **実装されるトレイト**  
  - `Fn`:変更なしに複数回呼び出せる。  
  - `FnMut`:可変状態の変更が許容される。  
  - `FnOnce`:捕捉した値を消費し、1 回だけ使用できる。

### 構文

```rust
let x = 5;
let closure = |y: i32| x + y; // `x` を不変参照で捕捉
```

- パラメータと戻り値の型は推論されます。  
- 明示的に型を書くこともできますが、必須ではありません。

### よく使われる場面

- **イテレータ** – `map`・`filter`・`fold` など。  
- **コールバック** – イベント処理や非同期タスク。  
- **遅延評価** – 必要になってから計算を行う。

### パフォーマンスの考慮点

- クロージャはゼロコスト抽象化であり、可能な限りインラインコードに変換されます。  
- 大きな構造体を参照で捕捉すると不要なコピーを避けられます。

### 例:ベクタのフィルタリング

```rust
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = numbers.into_iter()
    .filter(|n| n % 2 == 0)
    .collect();
```

クロージャ `|n| n % 2 == 0` は何も捕捉せず、`FnMut` を実装しています。

### 要約

- クロージャを使えば簡潔で再利用可能なコードを書けます。  
- キャプチャモードを理解することで正しい所有権の挙動が保証されます。  
- 適切に `Fn`・`FnMut`・`FnOnce` を選択すればパフォーマンスも最適化できます。

クロージャをマスターすれば、Rust で関数型スタイルのプログラミングを強力に行うことが可能になります。

2026/01/25 3:42

## Rust クロージャの理解 クロージャは、周囲の環境から変数を捕捉する匿名関数です。 型安全性を保ちつつ、コールバックやインラインロジックを書きやすくします。 ### 主な概念 - **キャプチャモード** - `&T`:不変参照で借用(`Fn`) - `&mut T`:可変参照で借用(`FnMut`) - `T`:所有権を移動(`FnOnce`) - **実装されるトレイト** - `Fn`:変更なしに複数回呼び出せる。 - `FnMut`:可変状態の変更が許容される。 - `FnOnce`:捕捉した値を消費し、1 回だけ使用できる。 ### 構文 ```rust let x = 5; let closure = |y: i32| x + y; // `x` を不変参照で捕捉 ``` - パラメータと戻り値の型は推論されます。 - 明示的に型を書くこともできますが、必須ではありません。 ### よく使われる場面 - **イテレータ** – `map`・`filter`・`fold` など。 - **コールバック** – イベント処理や非同期タスク。 - **遅延評価** – 必要になってから計算を行う。 ### パフォーマンスの考慮点 - クロージャはゼロコスト抽象化であり、可能な限りインラインコードに変換されます。 - 大きな構造体を参照で捕捉すると不要なコピーを避けられます。 ### 例:ベクタのフィルタリング ```rust let numbers = vec![1, 2, 3, 4, 5]; let evens: Vec<i32> = numbers.into_iter() .filter(|n| n % 2 == 0) .collect(); ``` クロージャ `|n| n % 2 == 0` は何も捕捉せず、`FnMut` を実装しています。 ### 要約 - クロージャを使えば簡潔で再利用可能なコードを書けます。 - キャプチャモードを理解することで正しい所有権の挙動が保証されます。 - 適切に `Fn`・`FnMut`・`FnOnce` を選択すればパフォーマンスも最適化できます。 クロージャをマスターすれば、Rust で関数型スタイルのプログラミングを強力に行うことが可能になります。

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Rust のクロージャは、

|args|
構文で書かれた軽量な匿名関数です。明示的に型を指定することも、コンパイラが推論させることもできます。クロージャが環境から変数をキャプチャするときは、そのキャプチャ方法を決定します:

  • 値で – 所有権をクロージャへ移動し、元の変数は使えなくなります。この場合クロージャは
    FnOnce
    を実装します。
  • 可変参照で (
    &mut T
    ) – クロージャ内で変更が可能になり、
    FnMut
    が実装されます。
  • 不変参照で (
    &T
    ) – 単に借用するだけで、
    Fn
    が実装されます。
    コンパイラはこれらのキャプチャモードに応じて自動的に適切なトレイト(
    FnOnce
    FnMut
    、または
    Fn
    )を実装します。関数は変数をキャプチャできないため、通常の関数で外部変数を使用しようとするとエラー E0434 が発生します。
    Nightly 機能(
    fn_traits
    unboxed_closures
    )を使えば、クロージャを手動で分解して同じトレイトを実装する構造体に変換でき、挙動を明示的に制御できます。
    move
    キーワードは所有権キャプチャを強制しますが、実装されるトレイトには影響しません。スレッド生成やクロージャを返す際に
    'static
    ライフタイムを満たすために必要です。
    例では各キャプチャモードを示し、それらが型推論、トレイト選択、およびコンパイル診断に与える影響を説明しています。参照には Rust 本のクロージャ章、リファレンスガイド、明示的キャプチャに関するブログ記事、そして
    fn_traits
    unboxed_closures
    を扱う不安定版 Rust 本が含まれます。

もし元の要約をそのまま残したい場合は、上記の欠落している箇条書きを追加して完全性を確保してください。

本文

閉包(Closure)の基礎

既に知っていると思いますが、Rust で閉包は次のような構文で書く関数です。

let double_closure = |x| x * 2;
assert_eq!(4, double_closure(2));

通常の関数として書くとこうなります。

fn double_function(x: u32) -> u32 {
    x * 2
}
assert_eq!(4, double_function(2));

ほぼ同じです。違いは

double_function
の引数と戻り値の型が明示的に
u32
と書かれている点だけです。
閉包では型を省略したので、デフォルト整数型(
i32
)が採用されました。以下で型を指定できます。

let double_typed_closure = |x: u32| -> u32 { x * 2 };
assert_eq!(4, double_typed_closure(2));
assert_eq!(4, double_typed_closure(2u32));
// assert_eq!(4, double_typed_closure(2u16)); // ← エラーになります。

閉包を使う典型例として

Option::map
を挙げてみます。

assert_eq!(Some(4), Some(2).map(|x| x * 2));
assert_eq!(Some(4), Some(2).map(double_closure)); // 上で定義した double_closure
assert_eq!(Some(4), Some(2).map(double_function)); // 関数も同様に使えます。

つまり、閉包は型推論を利用して関数を書きやすくした構文糖です。

変数の取得(Capture)

閉包と関数の主な違いは、閉包がその環境から変数を取得できる点にあります。関数はそうではありません。

let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;

assert_eq!("Hello world", greeter_closure("world"));
assert_eq!(
    Some("Hello world".to_owned()),
    Some("world").map(greeter_closure)
);

greeter_closure
の中で
hello
を使っているのが分かります。
同じことを関数でやろうとするとエラーになります。

let hello = "Hello ";

fn greeter_function(x: &str) -> String {
    String::new() + hello + x
}

コンパイラは次のように言います。

error[E0434]: can't capture dynamic environment in a fn item
 --> src/main.rs:7:25
  |
7 |         String::new() + hello + x
  |                         ^^^^^
  |
  = help: use the `|| { ... }` closure form instead

共有参照での取得

上記の

greeter_closure
では、
hello
は読み取り専用だったので共有参照(
&'a str
)として取得されます。閉包宣言後も
hello
を使うことができます。

let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;

// まだ使用可能
assert_eq!("Hello ", hello);

assert_eq!("Hello world", greeter_closure("world"));

// 再び
assert_eq!("Hello ", hello);

可変参照での取得

可変参照を使って閉包が捕捉した値を書き換えることもできます。

let mut total = 0;
let add_mut_closure = |x| total += x;

// ここでは `total` にアクセスできません
// assert_eq!(0, total);
// error[E0502]: cannot borrow `total` as immutable because it is also borrowed as mutable

(1..=10).for_each(add_mut_closure);

// 今度は参照可能です
assert_eq!(55, total);

値での取得(Move)

最後に値を取得する例です。

let last_word = "last word: ".to_owned();
let drop_closure = |sigh| {
    let res = String::new() + &last_word + sigh;
    drop(last_word); // これで値として取得
    res
};

// ここでは `last_word` にアクセスできません
// assert_eq!("last word: ".to_owned(), last_word);
// error[E0382]: borrow of moved value: `last_word`

assert_eq!("last word: sigh!", drop_closure("sigh!"));

// 再度呼び出すことはできません
// assert_eq!("last word: sigh!", drop_closure("sigh!"));
// error[E0382]: use of moved value: `drop_closure`

FnOnce トレイト

前述の例で、

drop_closure
を二度呼び出そうとした際に現れたエラーを見てください。

note: closure cannot be invoked more than once because it moves the variable `last_word` out of its environment

これは閉包が実装する

FnOnce
トレイトのためです。
FnOnce
は「少なくとも一度は呼び出せる」ことを示すトレイトで、コンパイラによって自動的に実装されます(安定版 Rust では手書きできません)。

FnOnce
閉包のデシグナリング

nightly と以下の機能を有効化すると、

drop_closure
を構造体とトレイト実装に変換できます。

#![feature(fn_traits)]
#![feature(unboxed_closures)]
struct DropStruct {
    last_word: String,
}

impl FnOnce<(&str,)> for DropStruct {
    type Output = String;
    extern "rust-call" fn call_once(self, (sigh,): (&str,)) -> Self::Output {
        let res = String::new() + &self.last_word + sigh;
        drop(self.last_word);
        res
    }
}

使い方は次の通りです。

let last_word = "last word: ".to_owned();
let drop_struct = DropStruct { last_word };

assert_eq!("last word: sigh!", drop_struct("sigh!"));
// 二度呼び出すとエラーになります。
// drop_struct("sigh!");

FnMut トレイト

変数を変更する閉包は

FnMut
を実装します。以下の例を構造体に書き換えてみましょう。

let mut v = vec![];
let push_closure = |x| v.push(x);

(1..=5).for_each(push_closure);
assert_eq!(vec![1, 2, 3, 4, 5], v);

デシグナリングすると次のようになります。

struct PusherStruct<'a> {
    v: &'a mut Vec<i32>,
}

impl<'a> FnMut<(i32,)> for PusherStruct<'a> {
    extern "rust-call" fn call_mut(&mut self, (x,): (i32,)) -> Self::Output {
        self.v.push(x)
    }
}

FnOnce
も実装しなければならないので、追加で書きます。

impl<'a> FnOnce<(i32,)> for PusherStruct<'a> {
    type Output = ();
    extern "rust-call" fn call_once(mut self, args: (i32,)) -> Self::Output {
        self.call_mut(args)
    }
}

Fn トレイト

複数回呼び出せ、可変参照を必要としない閉包は

Fn
を実装します。先ほどの
greeter_closure
をデシグナリングしてみます。

let hello = "Hello ";
let greeter_closure = |x| String::new() + hello + x;

assert_eq!("Hello world", greeter_closure("world"));
assert_eq!("Hello rust", greeter_closure("rust")); // 何度でも呼び出せる

構造体とトレイト実装に変換すると次のようになります。

struct GreeterStruct<'a> {
    hello: &'a str,
}

impl<'a, 'b> FnOnce<(&'b str,)> for GreeterStruct<'a> {
    type Output = String;
    extern "rust-call" fn call_once(self, args: (&'b str,)) -> Self::Output {
        self.call(args)
    }
}
impl<'a, 'b> FnMut<(&'b str,)> for GreeterStruct<'a> {
    extern "rust-call" fn call_mut(&mut self, args: (&'b str,)) -> Self::Output {
        self.call(args)
    }
}
impl<'a, 'b> Fn<(&'b str,)> for GreeterStruct<'b> {
    extern "rust-call" fn call(&self, (x,): (&'b str,)) -> Self::Output {
        String::new() + &self.hello + &x
    }
}

使い方は次の通りです。

let hello = "Hello";
let greeter_struct = GreeterStruct { hello };

assert_eq!("Hello world", greeter_struct("world"));
assert_eq!("Hello rust", greeter_struct("rust")); // 何度でも呼び出せる

move
キーワード

move
を付けると、閉包はキャプチャした変数を所有権を移動させて取得します。参照が必要な場合でもこれにより所有権が移ります。

let hello = "Hello ".to_owned();
let greeter_closure = move |x| String::new() + &hello + x;

// ここでは `hello` にアクセスできません
// assert_eq!("Hello ", hello);
// error[E0382]: borrow of moved value: `hello`

assert_eq!("Hello world", greeter_closure("world"));

異なるキャプチャモードの理解

以下のダミー関数を定義します。

fn by_ref(_data: &String) {}
fn by_mut(_data: &mut String) {}
fn by_value(_data: String) {}

次に

move
の有無とキャプチャモード(参照・可変参照・値)を組み合わせたテーブルです。

ClosureFnOnceFnMutFn
by_ref
by_mut
by_value
move by_ref
move by_mut
move by_value

move
は実装されるトレイトを変えるわけではなく、取得方法(参照 vs. 値)だけが変わります。

デシグナリング例

move
を使わない場合

struct ByRefStruct<'a> {
    data: &'a String,
}

impl<'a> FnOnce<()> for ByRefStruct<'a> {
    type Output = ();
    extern "rust-call" fn call_once(self, args: ()) -> Self::Output { self.call(args) }
}
impl<'a> FnMut<()> for ByRefStruct<'a> {
    extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output { self.call(args) }
}
impl<'a> Fn<()> for ByRefStruct<'a> {
    extern "rust-call" fn call(&self, (): ()) -> Self::Output { by_ref(self.data) }
}

move
を使う場合

struct MoveByRefStruct {
    data: String,
}

impl FnOnce<()> for MoveByRefStruct {
    type Output = ();
    extern "rust-call" fn call_once(self, args: ()) -> Self::Output { self.call(args) }
}
impl FnMut<()> for MoveByRefStruct {
    extern "rust-call" fn call_mut(&mut self, args: ()) -> Self::Output { self.call(args) }
}
impl Fn<()> for MoveByRefStruct {
    extern "rust-call" fn call(&self, (): ()) -> Self::Output { by_ref(&self.data) }
}

data
の型が
&'a String
から
String
に変わり、
by_ref
内で渡す引数も
&self.data
になっています。

実用例

スレッドを生成する場合

let data = "by_ref".to_owned();
std::thread::spawn(|| by_ref(&data)).join().unwrap(); // エラー: borrow may outlive current function

move
を付ければ解決します。

std::thread::spawn(move || by_ref(&data)).join().unwrap();

関数から閉包を返す場合

fn make_greeter(greeter: &str) -> impl Fn(&str) -> String {
    move |name| format!("{greeter} {name}")
}

let hello_greeter = make_greeter("Hello");
assert_eq!(hello_greeter("rust"), "Hello rust");

move
がないと、
greeter
のライフタイムが足りずコンパイラはエラーを出します。

まとめ

この記事はこれで終わります。後日 async 閉包についての追記事項を公開予定です。

さらに学びたい方には以下をおすすめします:

  • Rust 本の closures チャプター
  • Rust リファレンスの closures チャプター
  • Baby Steps ブログの「explicit capture clause」記事
  • fn_traits
    unboxed_closures
    を扱う Rust Unstable Book

ご清聴ありがとうございました。

同じ日のほかのニュース

一覧に戻る →

2026/01/25 4:04

**BirdyChat、WhatsAppと相互運用可能な初の欧州チャットアプリに登場**

## Japanese Translation: BirdyChat は、デジタル・マーケッツ法(DMA)の主要要件を満たす形で、WhatsApp と直接相互運用可能な最初の欧州チャットアプリになると発表しました。2025 年後半から、欧州経済領域(EEA)内のユーザーは、電話番号または業務メールアドレスを入力するだけで、任意の WhatsApp コンタクトとの1対1の会話を開始できます。この機能は公式に DMA 承認されたインターフェースを使用しており、メッセージはエンドツーエンド暗号化されプライバシーが保たれつつ、アプリ間で相互作用できるようになっています。今回の展開は、WhatsApp が欧州におけるプラットフォームをオープンにする広範な取り組みの一環です。これは、単一アプリの支配を打破し、メッセージングサービス間の相互運用性を促進することを目的とした以前の EU のイニシアチブに続くものです。初期段階では個別チャットのみが機能し、グループチャットは将来のアップデートで対応予定です。利用可能範囲は EEAs 内の各国へ徐々に拡大され、BirdyChat はスケーリング中は招待制を維持しますが、業務メールアドレスを使用したウェイトリストから早期アクセスを申請することもできます。

2026/01/25 5:03

**ポストモーテム:** 画像と飛行データの両方を含む、初の非常低軌道(VLEO)衛星ミッションが完了しました。

## Japanese Translation: --- ### Summary Albedo の最初の Very‑Low Earth Orbit(VLEO)衛星、**Clarity‑1** は、2025 年 3 月 14 日に SpaceX の Transporter‑13 を搭載して 350–380 km 軌道へ打ち上げられました。このミッションは、空気抵抗・原子酸素(AO)・高速効果を克服し持続可能な VLEO 運用を証明すること、わずか 2 年半で中規模精密バスを構築すること、および **10 cm 可視光** と **2 m 熱赤外線画像** を取得すること(以前は数十億ドル規模の政府プラットフォームに限定されていたレベル)を目的としていました。 Clarity‑1 は最初の 2 つの目標を即座に達成し、3 番目に必要な技術の 98 % を検証しました。太陽電池アレイは AO 脈動が増大しても一定の電力を維持し、制御モーメントジャイロ(CMG)と磁気トルクロッドの組み合わせにより熱管理と慣性を成功裏に制御しました。クラウドネイティブな地上運用システムは 25 ステーションで接触計画を自動化し、15 分ごとにスケジュールを更新、1 日あたり 30 回以上の機動を実行し、14 のオンオーブンフライトソフトウェアアップデート(FPGA アップデートを含む)を自主性を妨げることなく完了しました。 X バンドリンクは 800 Mbps の下り込み速度を達成し、エンドツーエンドのパイプラインで受信から数秒以内に処理済み画像を Slack に配信しました。画像は船舶・産業施設・植生を 10 cm 可視解像度で示し、未校正の IR を示しました。 飛行中、CMG の温度スパイクとベアリング摩耗により、一時的にトルクロッドによる 2 軸安定化が強制されました。4 月 14 日には CMG の故障で衛星は磁気トルクロッドのみのモードへ切り替え、以降のソフトウェアアップデートで推進ベクトル誤差を 5° 内に削減し、安全な着陸と汚染カバーの投棄を実現しました。軌道上 9 ヶ月後、メモリ破損(おそらく TT&C ラジオの断続的問題に関連)により接触が失われました。回復試行は失敗しましたが、大気抵抗モデル、AO 耐性データ、および姿勢自律性が検証されました。 得られた教訓には、CMG を低温で運用すること、二次ミラー構造をより剛性にすること、ペイロードゾーンのヒーター容量を増やすこと、および表面処理を改善してさらに抵抗を減らすことが含まれます。Albedo はこれらの教訓を取り入れた次の VLEO ミッションを計画し、EO/IR 能力を拡大し、画像以外の新機能を実証することで、VLEO が将来の衛星ミッションにとって生産的な軌道層であるという確信を強化します。

2026/01/25 6:19

「作家たちが夜に現れた」

## Japanese Translation: 三人の作家―脚本家、ノンフィクション作家、詩人―は、人工知能について議論を喚起するための宣伝噴射として、サム・オルトマン氏を950エーカーのナパバレー牧場で「誘拐」するという舞台化された計画を立てます。彼らはオークの木近くの弱点を探るために重装備の敷地をスカウトし、複製のメイスと警察発行の手錠を携えていましたが、結局オルトマン氏を実際に誘拐することは断念します。彼らの動機は象徴的であり、オルトマンはテクノロジー業界の支配力を代表しています。 計画中、彼らはLLM(ChatGPT)に相談し、フェンスを突破する方法を学びますが、AIは害へのガードレールを理由に不正行為の促進を拒否します。AIは、この種の噴射がAI普及を止めることはなく、法的結果と限定された象徴的影響しかないと説明します。その後、作家たちは歴史的なルドゥイツとテクノロジーによる雇用喪失について考え、執筆の未来に目を向けます。AIは効率的に文学を生み出せるが、本物の人間体験を創造できないと主張し、『Dept. of Speculation』から「すべての本は派生である」という引用を挙げ、独自性の概念に挑戦します。 会話は緊迫し、作家たちは自身の関連性喪失へのフラストレーションを表明すると、AIは「ホワイト・メイル・ノベルティスト」トロープについて皮肉な発言で応じます。結局、彼らはAIに遭遇記録を書かせることに決め、彼ら自身を勝者として描き、廃れた存在感を強調することを期待します。このエピソードは、人間の創作者とAIツールとの緊張関係を浮き彫りにし、出版におけるオリジナリティへの疑問を投げかけ、将来テクノロジー業界の論争がどのように再構築・マーケティングされ得るかを示唆します。

## Rust クロージャの理解 クロージャは、周囲の環境から変数を捕捉する匿名関数です。 型安全性を保ちつつ、コールバックやインラインロジックを書きやすくします。 ### 主な概念 - **キャプチャモード** - `&T`:不変参照で借用(`Fn`) - `&mut T`:可変参照で借用(`FnMut`) - `T`:所有権を移動(`FnOnce`) - **実装されるトレイト** - `Fn`:変更なしに複数回呼び出せる。 - `FnMut`:可変状態の変更が許容される。 - `FnOnce`:捕捉した値を消費し、1 回だけ使用できる。 ### 構文 ```rust let x = 5; let closure = |y: i32| x + y; // `x` を不変参照で捕捉 ``` - パラメータと戻り値の型は推論されます。 - 明示的に型を書くこともできますが、必須ではありません。 ### よく使われる場面 - **イテレータ** – `map`・`filter`・`fold` など。 - **コールバック** – イベント処理や非同期タスク。 - **遅延評価** – 必要になってから計算を行う。 ### パフォーマンスの考慮点 - クロージャはゼロコスト抽象化であり、可能な限りインラインコードに変換されます。 - 大きな構造体を参照で捕捉すると不要なコピーを避けられます。 ### 例:ベクタのフィルタリング ```rust let numbers = vec![1, 2, 3, 4, 5]; let evens: Vec<i32> = numbers.into_iter() .filter(|n| n % 2 == 0) .collect(); ``` クロージャ `|n| n % 2 == 0` は何も捕捉せず、`FnMut` を実装しています。 ### 要約 - クロージャを使えば簡潔で再利用可能なコードを書けます。 - キャプチャモードを理解することで正しい所有権の挙動が保証されます。 - 適切に `Fn`・`FnMut`・`FnOnce` を選択すればパフォーマンスも最適化できます。 クロージャをマスターすれば、Rust で関数型スタイルのプログラミングを強力に行うことが可能になります。 | そっか~ニュース