Rust プログラマーのための C

2026/10/08 22:44

Rust プログラマーのための C

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

要約▶

Japanese Translation:

最も重要な教訓は、C が安全性とメモリ管理のために手動管理を必要とする点であり、これに対し Rust はこれらの保証をネイティブに強制していることです。主な課題には、文字列の 널 ターミネーションを手動で処理することや、堅牢なネイティブ

Result
型ではなく「マジック整数」を用いてエラーチェックを行うことが挙げられます。さらに、標準的な整数サイズはプラットフォーム間で異なり、関数への引数として渡される際配列がポインタに変換(劣化)してしまうため、警告なし(例:Clang)ではサイズ計算が困難です。C99 で
<stdbool.h>
を介してボリアンヘッダーを後から追加したような歴史的な設計決定や、生ポインター算術の採用は、C が借用チェックや参照型といったモダンな安全性機能を欠く理由を説明します。したがって、メモリ割り当て関数(例:
malloc
)の戻り値をチェックしない場合、デベロッパーはバッファオーバーフローまたはセグメンテーションフォルトというリスクに直面します。これらの固有のリスクを軽減するため、C を使用する企業は手動のエラー処理を厳しく強制し、
<stdint.h>
から固定幅タイプを利用してプラットフォーム間の一貫性を確保する必要があります。これは、言語が不安全な使用パターンを制限するために可視修飾子を提供しないためです。

本文

Rust プログラマーのための「C の驚きと落とし穴」

多くのプログラマーが C や C++ から Rust に移るのに対し、著者は最初から Rust を学びました。そのため、「C 向けに書かれた Rust ア.rticle は多いが」、「Rust プログラマー向けの C の記事は少ない」という状況にあります。 この投稿では、独学で C を習得した過程で見つかった 「眉をひそめさせるような」 詳細な事項を集めました。

注意: これは正式な C チュートリアルではありません。詳細な仕様については独自に調べる必要があります。

真偽値 (
bool
) はプリミティブ型ではない(※)

C の初期バージョンでは、真偽値を表すプリミティブな型は存在せず、代わりに整数

0
と
1
を使用していました。 C99 で
<stdbool.h>
ヘッダーを追加することで
bool
が導入されましたが、
true
と
false
はキーワードではなく、以下のマクロ定義です
。

#define true 1
#define false 0

したがって、他の言語のようにリテラルとして振る舞いません。C23 ではプリミティブ型になりましたが、旧バージョンとの互換性を考慮し

<stdbool.h>
の使用が必要です。

Null テーミングされた文字列(Null-Terminated Strings)

Rust の

&str
は 16 バイト(メモリアドレス 8 バイト + 長さ 8 バイト)を使用します。これに対し、C では文字列のサイズを保存せず、すべての文字列を null バイト (
\0
) で終了させる
というアプローチをとります。

このトレードオフには以下のメリット・デメリットがあります。

メリット

  • 文字列の隣に追加のサイズ変数を保持する必要がありません。
  • strlen
    などで長さを効率的に取得できます。

デメリット・リスク

  • メモリ効率: 各文字列に null バイト分のスペースを確保する必要があります。空の文字列
    ""
    でも 1 バイト消費します。
  • 安全上の問題:
    • null バイトを文字列真ん中に使うと
      <string.h>
      の関数が誤動作します。
    • **終了記号 (
      \0
      ) を忘れると「範囲外読み取り」**を引き起こし、セキュリティリスクになります。

実装の注意点:メモリ確保と終了記号

C で文字列を逆転させる場合、null バイトを考慮した追加スペースを確保し、最後に必ず

\0
を追加する必要があります。

char* reverse(char* forward) {
    // 文字列の長さを計算(null テーミネータを除く)
    unsigned long len = strlen(forward);
    
    // 【重要】文字列分 + null バイト分のスペースを確保する
    char* reversed = malloc(len + 1); 

    for (int i = 0; i < len; i++) {
        reversed[i] = forward[len - 1 - i];
    }

    // 【重要】末尾に null テーミネータを追加する
    reversed[len] = '\0';

    return reversed;
}

対照的に、Rust 関数は

&str
の長さがメタデータに含まれているため、このような手動の管理が不要です。

ターゲット依存の整数幅

C の整数型(

int
,
long
など)は正確なビット数を保証されていません。幅はターゲットプラットフォームによって異なります。特に Unix (
long
が 64 ビット) と Windows (
long
が 32 ビット) で挙動が変わるのは危険です。

クロスプラットフォーム互換性のための推奨事項

可変長の整数ではなく、**固定幅整数(

stdint.h
提供)**を使用する必要があります。

RustC (固定幅)
u8
...
u64
,
usize
uint8_t
~
uint64_t
,
size_t
i8
...
i64
,
isize
int8_t
~
int64_t
,
ptrdiff_t

エラー処理は酷いものです

Rust の

Result
と enum/match がエラー処理を強制し、安全な設計を促すのに対し、C のエラー処理は 「魔法の整数」に依存しています。

C の欠陥あるエラー処理

  • 関数は
    -1
    や
    NULL
    などを返してエラーをシグナル化します。
  • errno
    を確認するだけで情報が限られます(スタックトレースや詳細なメッセージは困難)。
  • 言語がエラー処理を強制しません。失敗可能性のあるリソース操作(例:
    malloc
    )の後、手動でエラーチェックと NULL 処理を行う必要があります。
char* reversed = malloc(len + 1);

// 【必須】メモリ不足時の NULL チェック
if (reversed == NULL) {
    perror("Error"); // エラーメッセージを表示
    exit(1);          // 優雅に終了
}

NULL ポインタチェックをしないと、セグメンテーションフォールト(Segfault)が発生します。 より安全な設計を試みるために「タグ付きユニオン」で Rust の

Result
を模倣することもできますが、C は型システムによる強制力がなく、意図的な設計欠陥と見なされます。

フィールドアクセス構文

C では、値アクセス (

.
) と ポインターアクセス (
->
)
に演算子が異なります。Rust などのようにすべてに
.
を使うことはできません。

struct Foo {
    int field;
};

struct Foo value = { 103 };      // 値に対しては . でアクセス
struct Foo* ptr = &value;        // ポインターに対しては -> でアクセス

printf("%d\n", value.field);   // OK: 値アクセス
printf("%d\n", ptr->field);    // OK: ポインタ経由のフィールドアクセス

アレイは関数内ではポインター化される(ふざけて)

C のアレイには奇妙な挙動があります。

sizeof()
はコンテキストによって意味が異なります。 一般的に、アレイは「定義された場所」でサイズを持ちますが、関数のパラメータとして渡されるとポインタに変換されます。

メイン関数内での動作(正しい)

void print_sizes() {
    char declared_size[3] = { 1, 2, 3 };
    char inferred_size[]   = { 4, 5, 6 };

    // アレイのサイズは実際に保持されている
    printf("Declared size (main): %ld bytes\n", sizeof(declared_size)); // 3 bytes
    printf("Inferred size (main): %ld bytes\n", sizeof(inferred_size)); // 3 bytes
}

関数引数での動作(ポインター化)

void function(char declared_size[3], char inferred_size[]) {
    // 【重要】ここではアレイではなくポインターとして扱われるため sizeof はポインタサイズ
    printf("Declared size (function): %ld bytes\n", sizeof(declared_size));  // 8 bytes (64bit システム)
    printf("Inferred size (function): %ld bytes\n", sizeof(inferred_size));  // 8 bytes
}
  • 原因:
    void function(char declared_size[3], ...)
    の型定義は、コンパイル時に自動的に
    void function(char* declared_size, ...)
    に変換されます。
  • 対策: Clang コンパイラはこれを検出し、警告を出力します。
$ clang main.c
warning: sizeof on array function parameter will return size of 'char *' instead of 'char[3]' [-Wsizeof-array-argument]

おや、参照は存在しないではありませんか?

C には 参照(Reference)の概念はありません。すべてが生ポインターです。バウンディングチェックもありません。

そのため、以下のような「楽しい」ポインタ算術が可能になりますが、非常に危険です。

int array[] = { 0, 1, 2, 3, 4, 5 };

// インデックス操作ではなく、直接的なポインタ算術
for (int* x = &array[0]; x < &array[6]; x++) {
    *x = 5 - *x;
}

※ これを実行するとバグやセキュリティホール(バッファオーバーフロー)の原因となります。

結論

C を学ぶのは非常に面白かったですが、システムプログラマーとして知っておくべき必須の知識でもあります。 著者の GitHub リポジトリには実装例が公開されていますので、興味があればぜひ触ってみてください。


BD103 :)


フットノート

  1. 技術的にはヘッダーなしで
    _Bool
    を使用できますが、
    true
    /
    false
    マクロを使用するには
    <stdbool.h>
    が必要です。↩
  2. Clang は C23 の完全サポートを持っていないため、C23 に移行するまで
    <stdbool.h>
    への依存は継続される可能性があります。↩
  3. Rust 版の逆転関数は簡易的な実装です。本番環境では
    forward.chars().rev().collect::<String>()
    が推奨されます。↩
  4. usize
    と
    isize
    は C の
    size_t
    や
    ptrdiff_t
    と完全にはマッピングされません。使用には注意が必要です。↩
  5. Rust の型システムを「Fearless SIMD」のようなコード検証に利用する事例も参照推荐阅读します。↩

同じ日のほかのニュース

一覧に戻る →

2026/10/11 7:50

独自の意思決定モデルを構築する

## Japanese Translation: 本研究の核心となる洞察は、言語モデルは単一パスの意思決定システムを模倣することは可能であるが、特定のカリブレーションが行われる限りでは、しばしば危険な過剰な自信を示すという点にある。標準的なモデルが複数のパスを通じて順次テキストを生成するのに対し、システムワンアプローチは制約付きデコーディング(例えば、選択肢 A〜E の語彙をマスキングする)を用いて、AI に固定されたオプションを 1 パスで選択させる。この手法は推論速度を向上させるが、信頼スコアの膨張というリスクをもたらす;具体的には、ネイティブ出力トークンの確率は次のトークンに対する自信を反映しており、正しい答えの真なる確率を反映していない。CommonsenseQA の保持サンプルでの評価では、ファインチューニングの後であっても未カリーブレーテッドなモデルは、明確な単一の答えが存在しない困難な Commonsense 問題に対して高い不確かさ(例:99.78%)を割り当てることができ、マクロ F1 精度は約 58%に留まり、高自信ビンに至っては単なる 70%に過ぎなかった。本研究では、LLM 模倣(Qwen/Qwen3-1.7B)における温度スケイリングを用いてこの問題を成功裏に解決し、モデルの報告された自信を実際の性能と数学的に整合させ、結果として適合温度が約 3.8 となった。したがって、制約付きデコーディングは標準化テストのような多選択タスクに対して効率的を提供するものの、その後のカリブレーションなしで展開することは、過剰な自信による誤りを招き、ユーザーを欺くことになる。今後、事前に定義された答えへの厳格な遵守が求められる適用においては、信頼性指標が真に信頼性を反映するように、事後処理ステップ(例えば温度スケイリング)の優先を確保する必要がある。

2026/10/07 21:30

2D 車両

## Japanese Translation: 「Motion Lab」は、1996 年に GFA BASIC で書かれた先駆的な物理エンジンが GTA の車体システムを動力源としていたのを記念し、同エンジンの 30 周年を祝うためのモダンな Web ベースの再現作品です。当時の一般的なシンプルな「ポインタ・フィジックス」と異なり、この JavaScript インプリメンテーションは、リアルなトルクおよび力の相互作用を含む高度な古典的な 2 次元剛体動力学を正確にシミュレートします。本プロジェクトは、教育的目的のためにレガシースタイルを維持しつつ、オリジナルのソフトウェアが後に摩擦に関する推測に基づいた近似的かつ技術的に不正確な車体シミュレーション層を追加したことを認める一方で、「リマスター」された、より美しいバージョンの元のワイヤフレーム美学を提供しています。ユーザーは「Car(カー)」「Spaceship(スペースシップ/通称:Ship)」「Brick(ブリック)」という 3 つの異なるモードを体験でき、これらのモードは歴史的に「Brick モデル」から始まり、「Ship 要素」を含むよう進化し、最終的に「Car 要素」を取り入れた経緯を持っています。体験には、重力やバリアーの有効/無効化に加え、キーボードまたはタッチ入力により制御される GTA スタイルのカメラズームが含まれています。重要なのは、この非公式なプロジェクトが Rockstar Games および Take-Two から独立しており、オフィシャル製品ではなくトリビュートであることです。2026 年の発表を予定している本再現作品は、ユーザーが外部プラグインに依存せず、数十年にわたる技術開発を直接体験することを呼びかけています。

2026/10/11 5:31

あなたは存在したくても、街自体がそれを好まない街建設ゲーム

## Japanese Translation: サンフランシスコ当局は、特定の住宅シミュレーターに関する自由裁量審査を正式に開始し、都市の規制環境におけるその影響を検討するための重要な手続き的段階を示しています。この措置は、政府機関が該ツールを積極的に調査していることを示しており、同時に具体的な欠陥や直ちに懸念すべき事項がまだ特定されていないことも指摘しています。当面の次の段階では、審査プロセスを継続してシミュレーターの法的地位および運用可能性を決定することとなり、これが将来的にサンフランシスコにおける同様の住宅シミュレーターの開発と規制方法に影響を与える可能性があります。