第7話:キーと値でデータを管理!超重要データ構造「マッピング (mapping)」を使いこなす

2025年7月31日

概要

数百万ユーザーのデータをどう管理する?その答えが、Solidity最重要のデータ構造「マッピング」です。キー(例: address)と値(例: uint)でデータを紐付ける仕組みと、msg.senderを使ったユーザーごとのデータ書き込み・読み出し方法を解説します。

requireによる入力検証を交え、簡単な銀行DAppを実装。複数人が参加するアプリケーションの核心技術をマスターしましょう。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

はじめに

第6話までで、あなたは関数の可視性をマスターし、コントラクトのセキュリティを固める方法を学びました。あなたのコードは、より堅牢で、プロフェッショナルなものになったはずです。

しかし、ここである大きな壁に突き当たります。これまでのコントラクトは、countownerといった、単一のデータしか扱えませんでした。これでは、本当のアプリケーションは作れません。

例えば、ERC20トークンのような「通貨」を作りたいと考えたとしましょう。その場合、コントラクトは**数万人、数百万人のユーザーそれぞれが「いくら持っているか」**を記録しなければなりません。あなたならどうしますか? ユーザー全員のアドレスを配列に入れて、残高も別の配列に入れて…? そんなことは非現実的で、ガス代が天文学的な数値になってしまいます。

この課題を、極めてエレガントかつ効率的に解決するのが、今回学ぶ**mapping(マッピング)**です。

マッピングは、Solidityにおける最重要データ構造の一つです。これをマスターすることは、単なる入門者から、トークンやDeFi、NFTといった実用的なDAppを構築できる開発者へと飛躍することを意味します。

mappingとは? 無限に広がる仮想辞書

mappingとは、一言で言えば**「キーと値のペア」を保存するためのデータ構造**です。他の言語に触れたことがあるなら、「ハッシュマップ」や「辞書(Dictionary)」、「連想配列」のようなものだと考えると非常に分かりやすいでしょう。

mappingは、ある「キー(Key)」を指定すると、それに対応する「値(Value)」を瞬時に保存したり、取り出したりすることができます。

その構文は以下の通りです。

mapping(キーのデータ型 => 値のデータ型) アクセス修飾子 変数名;

最も古典的で重要な例が、ユーザーの残高を管理するためのマッピングです。

Solidity
// address型をキーとして、uint型を値として保存するマッピング
mapping(address => uint) public balances;

この一行が意味するのは、「balancesという名前のマッピングを定義します。これは、キーとしてaddressを受け取り、値としてuintを返します」ということです。

つまり、AさんのアドレスをキーにすればAさんの残高(uint)が、BさんのアドレスをキーにすればBさんの残高が返ってくる、という仕組みをたった一行で実現できるのです。まるで、ユーザーのアドレスが索引になった、無限にページを増やせる巨大な辞書のようです。

mappingの基本的な使い方

それでは、簡単な銀行コントラクトを例に、mappingの使い方を見ていきましょう。

1. mappingへのデータの書き込み

mappingへのデータ書き込みは、驚くほどシンプルです。配列の要素にアクセスするように、[] を使います。

マッピング名[キー] = 値;

では、「自分のお金を預ける(deposit)」機能を作ってみましょう。

Solidity

contract SimpleBank {
    mapping(address => uint) public balances;

    function deposit(uint _amount) public {
        // 「誰が」預けたのか? それを特定するのが msg.sender です
        balances[msg.sender] = balances[msg.sender] + _amount;
    }
}

ここで、msg.senderという非常に重要なグローバル変数が登場しました。msg.senderは、**「今、この関数を呼び出しているアカウントのアドレス」**が自動的に入る、address型の特別な変数です。

ユーザーがdeposit関数を呼び出すと、Solidityは自動的にそのユーザーのアドレスをmsg.senderにセットします。これにより、コントラクトは「誰が」操作しようとしているのかを常に把握できるのです。上記のコードは、「この関数を呼び出した人(msg.sender)の残高を、現在の残高に_amountだけ足したものに更新する」という意味になります。

2. mappingからのデータの読み取り

データの読み取りも、書き込みと同じくらい簡単です。

マッピング名[キー]

とすれば、対応する値を取得できます。

私たちのbalancesマッピングはpublicで宣言されているため、Solidityが自動的にゲッター関数を作成してくれます。Remixでコントラクトをデプロイした後、「balances」というボタンに調べたいユーザーのアドレスを貼り付けて「call」ボタンを押せば、その人の残高を誰でも確認できます。

もし、まだデータが書き込まれていないキーを指定したら? ここで重要なのは、mappingは「存在しない」という状態を持たないことです。まだ一度も書き込みがされていないキーを読み出そうとすると、エラーになるのではなく、その値のデータ型のデフォルト値が返ってきます。

  • uintなら 0
  • boolなら false
  • stringなら ""(空文字列)
  • addressなら 0x0000...(ゼロアドレス)

したがって、ユーザーの残高をチェックして、0が返ってきた場合、それは「残高がゼロ」なのか「まだ一度も取引がない」のかを区別できない、ということになります。

実践:入出金機能を持つ銀行コントラクト

預金(deposit)と出金(withdraw)の機能を持った、より実践的な銀行コントラクトを書いてみましょう。

Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SimpleBank {
    mapping(address => uint) public balances;

    // 預金する関数
    function deposit(uint _amount) public {
        // 0より大きい金額でなければエラーにする
        require(_amount > 0, "Deposit amount must be greater than 0.");
        balances[msg.sender] += _amount; // balances[msg.sender] = balances[msg.sender] + _amount; の短縮形
    }

    // 出金する関数
    function withdraw(uint _amount) public {
        // 自分の残高を取得
        uint userBalance = balances[msg.sender];
        // 残高が出金額以上であることを確認
        require(userBalance >= _amount, "Insufficient balance.");
        // 残高を減らす
        balances[msg.sender] = userBalance - _amount;
    }
}

ここでrequire()という便利な関数を使いました。require(条件, "エラーメッセージ")は、もし条件が満たされない(falseである)場合、そこで処理を停止し、すべての変更を元に戻してエラーを返すという機能です。これにより、「残高が足りないのに出金できてしまう」といった致命的なバグを簡単に防ぐことができます。

mappingの重要な特性(長所と短所)

  • 長所:
    • キーを指定した値の読み書きが、極めて高速でガス効率が良い
  • 短所:
    • ループ(繰り返し処理)ができないmappingに保存されている全てのキーや値の一覧を取得することは、設計上不可能です。
    • 保存されている要素の数を取得できないbalances.lengthのようなことはできません。

mappingは、あくまでキーを知っている上での単一データの出し入れに特化した、非常に割り切ったデータ構造なのです。(一覧を取得したい場合は、別途キーを配列に保存するなどの工夫が必要になりますが、それは応用編です。)

まとめ:複数ユーザーを扱うアプリへの扉

お疲れ様でした!あなたは今、Solidityで最も強力で、最も頻繁に使われるデータ構造であるmappingをマスターしました。

mapping(address => uint)

この一行が、ERC20トークンを始めとする、あらゆるWeb3アプリケーションの根幹を支えています。ユーザーごとのデータを効率的に管理する術を身につけたことで、あなたの作れるアプリケーションの幅は、無限に広がりました。

さて、現状のマッピングでは、キー一つに対して、uintstringといった単一の値しか保存できません。もし、「ユーザーごとに、名前(string)、残高(uint)、会員レベル(uint)、最終ログイン日時(uint)といった複数の情報をまとめて管理したい」場合はどうすればよいでしょうか?

次回、**第8話「データを整理整頓!複雑な情報を扱う「構造体 (struct)」で独自データ型を作る」**では、関連する複数の変数を一つにまとめた、あなただけのオリジナルなデータ型を作る方法を学びます。

目次
Solidity学習講座:最終版 目次(全20話)
Solidity学習講座:最終版 目次(全20話)

基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...

続きを見る

-Solidity