🪜

Swiftのdeferを使うときにどうスコープを作るのが良いのか

2023/04/28に公開

答えは出てません。→2023/5/2 答え出ました。

何のことはない、do {}を使えば良かったです。
C++やC#のtryと同様、catchブロックとペアで使うものかと思ってましたが、doブロック単体で使えました。これでいいですね。

元の話

例えば、C++で、コンストラクタでトランザクションを開始して、もしエラーがなければコミット、コミットされないままデストラクタまで来たらロールバックする、みたいな、Openerクラスを使う場合は以下のようになります。

void doSomething(DatabaseSession& session) {
  // 前処理とか
  ....
  // ここでトランザクション開始
  {
    // スタック上にできるので、スコープを抜けるとデストラクタが呼ばれる。
    // この例ではコンストラクタでトランザクションを開始することを想定。
    TransactionOpener opener(session);
    // 何か処理
    ...
    if (error) {
       return;
    }
    if (noErr) {
      opener.commit();
    }
    // コミットされないままスコープから抜けたらロールバック
  }
}

Swiftではdeferという構文があり、上記のようなことができます。ただ、deferが実行される条件であるところの「スコープから抜けたら」の「スコープ」を定義するための構文がない(気がする)のが気になります。

文末の区切り文字(C++の;相当)がないのと、最後の引数クロージャはカッコをつけなくて良い、という2つの文法上の特性のため(おそらく)、{}がどこでもクロージャ定義になってしまい、C++のように{}をスコープを設定するのに使えなくなってしまっている、という感じではないかと思います。

厳密に言えば、{}がクロージャを必要としているところにあるのか、そうでないのかはSwiftコンパイラは分かっていると思いますが(何でもないところに置くとエラーが出ますし)、これをエラーとしないでスコープ設定用のブロックとしてしまうと、ifの後ろに{がないケース同様、見落としエラーを作り込みやすいので、もうブロックにさせないよ!みたいなことなのかなと推測します。

そうすると、このdeferを実行したいがためにスコープを限定しようとしたときには、

if true {
  doSomething()
  defer {
    defferedSomething()
  }
  doAnything()
}

のような、if true {}だとかrepeat {} while falseだとか、得体の知れない(というかダミーの)制御構文の力が必要な気がします。あるいは、このブロックを丸々関数にするとか。

関数にする、が一番のような気もしますが、10行20行の処理で一部ちょっとスコープを狭めたいだけなのに、っていうケースでは回りくどいという場合もあります。

ifやwhileが来たら仮にtrue/falseしか書いてないとしても、いったんは条件を読んでどういう意味か考えてしまいます。

可読性が良くお手軽な方法があればいいのにねと思います。

Discussion