[Solidityのはじめ方] Part7: 関数定義(public, private, view, pure)

スマートコントラクトの心臓部、関数の基本をしっかり理解しよう 🧱

ステップ2へようこそ!ここでは、Solidityスマートコントラクトの動作を定義する「関数」について学びます。関数は、コントラクトが実行できる具体的なアクションを記述するコードのまとまりです。 今回は、関数の「誰がアクセスできるか(可視性)」と「状態変数を変更するかどうか(状態変更可能性)」を制御するための重要なキーワードを解説します。これらを正しく設定することは、安全で効率的なコントラクトを作る上で非常に重要です。


Warning: Undefined array key “is_admin” in /home/c2261046/public_html/omomuki-tech.com/wp-content/themes/sango-theme/library/gutenberg/dist/classes/Toc.php on line 113

Warning: Undefined array key “is_category_top” in /home/c2261046/public_html/omomuki-tech.com/wp-content/themes/sango-theme/library/gutenberg/dist/classes/Toc.php on line 118

Warning: Undefined array key “is_top” in /home/c2261046/public_html/omomuki-tech.com/wp-content/themes/sango-theme/library/gutenberg/dist/classes/Toc.php on line 124

1. 関数の基本的な書き方

まず、Solidityでの関数の基本的な形を見てみましょう。

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

contract FunctionSyntax {
    uint public myNumber = 10;

    // 関数の定義例
    function add(uint _a, uint _b) public pure returns (uint) {
        uint result = _a + _b;
        return result;
    }

    function getNumber() public view returns (uint) {
        return myNumber;
    }

    function setNumber(uint _newNumber) public {
        myNumber = _newNumber;
    }
}

基本的な構成要素は以下の通りです。

  • function: 関数定義を開始するキーワード
  • add, getNumber, setNumber: 関数の名前
  • (uint _a, uint _b): 引数(パラメータ)。データ型と名前を指定します。不要な場合は()とします。
  • public, pure, view: アクセス修飾子や状態変更可能性修飾子(後述)
  • returns (uint): 戻り値の型を指定します。戻り値がない場合は省略します。
  • { ... }: 関数の処理内容を記述するコードブロック

では、次に重要な修飾子について詳しく見ていきましょう。

2. アクセス修飾子(可視性)

アクセス修飾子は、関数をどこから呼び出すことができるかを制限します。Solidityには4つのアクセス修飾子があります。

public (公開) 🌐

最も制限の緩い修飾子です。public関数は、コントラクト内部、継承したコントラクト、およびコントラクト外部(他のコントラクトやユーザーアカウント)のどこからでも呼び出すことができます。状態変数にpublicをつけると、コンパイラが自動的にその変数の値を取得するためのgetter関数を生成します。

contract PublicExample {
    uint public data = 10; // 自動で `data()` getter関数が生成される

    function getValue() public view returns (uint) {
        return data; // 内部からも呼び出し可能
    }
}

private (非公開) 🔒

最も制限の厳しい修飾子です。private関数は、その関数が定義されたコントラクト内部からのみ呼び出すことができます。継承したコントラクトからでさえ呼び出すことはできません。機密性の高い内部ロジックに使用されます。

contract PrivateExample {
    uint private secretValue = 42;

    function _updateSecret(uint _newValue) private {
        secretValue = _newValue;
    }

    function performUpdate(uint _val) public {
        _updateSecret(_val); // 同じコントラクト内からは呼び出せる
    }

    // 外部や継承コントラクトからは _updateSecret を直接呼び出せない
}

注意: privateinternalは、他のコントラクトからのアクセスを防ぐだけであり、ブロックチェーン上のデータ自体は公開されているため、完全に隠蔽されるわけではありません。

internal (内部) 🏠

privateに似ていますが、internal関数は定義されたコントラクト内部だけでなく、そのコントラクトを継承したコントラクトからも呼び出すことができます。コントラクト外部からは呼び出せません。継承を前提とした内部ロジックに適しています。状態変数のデフォルトの可視性はこのinternalです。

contract InternalBase {
    uint internal internalData = 20;

    function _internalAction() internal pure returns (uint) {
        return 5;
    }
}

contract DerivedContract is InternalBase {
    function accessInternal() public view returns (uint) {
        // 継承したコントラクトから internal メンバーにアクセス可能
        uint baseData = internalData;
        uint actionResult = _internalAction();
        return baseData + actionResult;
    }
}

external (外部) 🚪

この修飾子が付いた関数は、コントラクト外部からのみ呼び出すことができます(他のコントラクトやトランザクション経由)。コントラクト内部から直接呼び出すことはできません(this.functionName()という形であれば呼び出せますが、非効率です)。主にコントラクトのインターフェースとして公開する関数に使用されます。外部呼び出しの場合、publicよりもガス効率が良いことがあります。

contract ExternalExample {
    function externalCallOnly(uint _x) external pure returns (uint) {
        return _x * 2;
    }

    function tryInternalCall() public pure returns (uint) {
        // return externalCallOnly(5); // これはコンパイルエラーになる
        return this.externalCallOnly(5); // this経由なら可能だが非推奨
    }
}

関数にアクセス修飾子を指定しない場合、デフォルトでpublicになりますが、意図しないアクセスを防ぐため、常に明示的に指定することが推奨されます。

3. 状態変更可能性修飾子

これらの修飾子は、関数がブロックチェーンの状態(コントラクトに保存されているデータ)を読み取るか、変更するかを宣言します。ガス効率やセキュリティに関わる重要な要素です。

view (閲覧) 👀

view修飾子が付いた関数は、コントラクトの状態変数を読み取ることはできますが、変更することはできません。状態を変更しようとする操作(状態変数への書き込み、イベントの発行、他のコントラクトの作成など)が含まれているとコンパイルエラーになります。外部から呼び出された場合、ガスを消費しません(読み取り専用操作のため)。

contract ViewExample {
    uint public count = 0;

    function getCount() public view returns (uint) {
        return count; // 状態変数(count)の読み取りはOK
    }

    // function tryIncrement() public view {
    //     count++; // view関数内で状態変更しようとするとコンパイルエラー
    // }
}

pure (純粋) ✨

pure修飾子が付いた関数は、コントラクトの状態変数を読み取ること、変更することできません。関数の実行結果は、入力された引数と関数内のローカル変数のみに依存します。数学的な計算や、状態に依存しないロジックに適しています。viewと同様に、外部から呼び出された場合はガスを消費しません。

contract PureExample {
    uint public value = 5;

    function add(uint _a, uint _b) public pure returns (uint) {
        return _a + _b; // 引数のみを使用
    }

    // function tryReadState() public pure returns (uint) {
    //     return value; // pure関数内で状態変数を読み取ろうとするとコンパイルエラー
    // }
}

修飾子なし (状態変更可能) ✏️

viewpureも付いていない関数は、デフォルトで状態変数を読み取り、変更することができます。これらの関数を呼び出すトランザクションは、ブロックチェーンの状態を変更する可能性があるため、ガスを消費します。

contract StateChangeExample {
    uint public counter = 0;

    // viewでもpureでもない関数は状態を変更できる
    function increment() public {
        counter++; // 状態変数(counter)を変更
    }
}

payable (支払い可能) 💰

これは状態変更可能性修飾子の一種で、関数がEtherを受け取ることができることを示します。payableが付いていない関数にEtherを送ろうとするとトランザクションは失敗します。Etherを受け取るロジック(例:トークン購入、寄付受付)に必要です。

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

    function deposit() public payable {
        balances[msg.sender] += msg.value; // 送られてきたEtherを記録
    }
}

payableについては、より詳細な説明が後のステップで出てきます。)

コンパイラは、関数が状態を変更しないのにviewpureが指定されていない場合や、状態を読み取らないのにpureが指定されていない場合に警告を出します。これらの警告に従い、可能な限り最も制限的な修飾子を使用することで、意図しない状態変更を防ぎ、ガス効率を高めることができます。

関数のアクセス修飾子と状態変更可能性修飾子の組み合わせを理解することは、Solidity開発の基本です。適切な修飾子を選択することで、コントラクトの安全性と効率性を向上させることができます。

修飾子 説明 アクセス範囲 状態変更 状態読取 ガス消費(外部呼出)
public どこからでも呼び出し可能 内部/外部/継承 (状態変更時)
private 定義されたコントラクト内部のみ 内部のみ – (外部から呼べない)
internal 内部および継承コントラクトから 内部/継承 – (外部から呼べない)
external コントラクト外部からのみ 外部のみ (状態変更時)
view 状態を読み取るが変更しない (可視性に依存) 不可 不要
pure 状態を読み取らず変更もしない (可視性に依存) 不可 不可 不要
payable Etherを受け取れる (可視性に依存)

* view / pure / payable はアクセス修飾子(public, external など)と組み合わせて使用します。
* 状態を変更しないpublic/external関数でも、内部的に他の状態変更関数を呼び出すなど、トランザクション全体として状態が変更される場合はガスが必要です。view/pure関数の外部呼び出し自体はガスを消費しません。

参考情報

お疲れ様でした!🎉 これで関数の基本的な定義方法、アクセス制御、状態変更の制御について理解できたはずです。

次のステップでは、計算や条件に応じた処理を行うための演算子・条件分岐・ループについて学びます。プログラミングの基本的な要素をさらに深掘りしていきましょう!