概要
数百万ユーザーのデータをどう管理する?その答えが、Solidity最重要のデータ構造「マッピング」です。キー(例: address)と値(例: uint)でデータを紐付ける仕組みと、msg.senderを使ったユーザーごとのデータ書き込み・読み出し方法を解説します。
requireによる入力検証を交え、簡単な銀行DAppを実装。複数人が参加するアプリケーションの核心技術をマスターしましょう。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る
目次
はじめに
第6話までで、あなたは関数の可視性をマスターし、コントラクトのセキュリティを固める方法を学びました。あなたのコードは、より堅牢で、プロフェッショナルなものになったはずです。
しかし、ここである大きな壁に突き当たります。これまでのコントラクトは、countやownerといった、単一のデータしか扱えませんでした。これでは、本当のアプリケーションは作れません。
例えば、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なら0boolならfalsestringなら""(空文字列)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アプリケーションの根幹を支えています。ユーザーごとのデータを効率的に管理する術を身につけたことで、あなたの作れるアプリケーションの幅は、無限に広がりました。
さて、現状のマッピングでは、キー一つに対して、uintやstringといった単一の値しか保存できません。もし、「ユーザーごとに、名前(string)、残高(uint)、会員レベル(uint)、最終ログイン日時(uint)といった複数の情報をまとめて管理したい」場合はどうすればよいでしょうか?
次回、**第8話「データを整理整頓!複雑な情報を扱う「構造体 (struct)」で独自データ型を作る」**では、関連する複数の変数を一つにまとめた、あなただけのオリジナルなデータ型を作る方法を学びます。
-

-
Solidity学習講座:最終版 目次(全20話)
基礎編 第1話:未来のインターネットへようこそ!Solidityとスマートコントラクトの全体像 第2話:準備は1分!ブラウザだけで開発できる「Remix IDE」の基本操作 第3話:記念すべき初コント ...
続きを見る

