LuneScript のスレッドにおける mutable 制御

Page content

LuneScript は golang へのトランスコンパイルをサポートしている。

golang 対応の付加機能として、LuneScript には限定的な非同期処理を提供している。

今回は、この「限定」を緩和する方法を検討する。

非同期処理を「限定」する理由

非同期処理を限定する主な理由は、非同期処理を安全に実行するためだ。

では、非同期処理のなにが危険なのかといえば、データアクセスの競合だ。

Rust では、データアクセスの競合が発生しないように、 言語の syntax で論理的に競合を排除する方法を採用している。

LuneScript も、同じように言語の syntax で論理的に排除できるように目指したい。

ただ、 Rust の syntax は、 データアクセスの競合排除と、 メモリアロケーションコントロールを行なう上では非常に有用ではあるが、 プログラミングのコストが高いのも事実だ。

LuneScript の目的は、楽をして安全に開発することなので、 安全性とのトレードオフで、もう少しコストの低い方法で実現したい。 特に LuneScript はメモリアロケーションコントロールは gc に任せているので、 Rust ほどの厳密な syntax は不要なので、その分の簡易化はすべきだ。

非同期処理を安全に実現する方法

非同期処理を安全に実現するには、 あるデータ A に対し、 非同期に mutable なアクセスが行なわれないことが保証できれば良い。

言い換えれば、 immutable なアクセスだけであれば安全である。

LuneScript には、メソッドの mut 宣言による mutable 制御がある。 これは、immutable な型のオブジェクトから、 mut 宣言されたメソッドのコールを禁止するものである。 mut 宣言されたメソッドは、 オブジェクトのメンバーを変更することを宣言するものである。

つまり、非同期処理に渡す引数を immutable 型のオブジェクトに限定し、 どこからもそのオブジェクトの mutable メソッドをコールしないようにすれば、 安全に非同期処理が実現できるように考えられる。 しかし、これを実現するのもそう簡単ではない。

その原因は次にある。

  • あるオブジェクトを、複数の mutable 型の変数に代入できる
  • メソッドの mutable 制御だけでは対処できないケースがある

複数の mutable 型の変数に代入できる

LuneScript では、 あるクラスのオブジェクトを、 複数の mutable 型の変数に代入することが出来る。 これにより、あるオブジェクトが意図していない所で mutable 型の変数に代入され、 その変数を通して mut 宣言されたメソッドがコールされ、 非同期処理に影響を及ぼす可能性がある。

これを防止するのがまさに Rust が採用する所有権制御である。 ただ前述しているように、 これはコストが大きいので LuneScript では採用したくない。

これに関しては、制限として割り切る方向で考えている。

ただ、完全に割り切ってユーザに管理を丸投げするのではなく、 なんらかの設計の手助けになる情報を提供するツールを別途検討する。

メソッドの mutable 制御だけでは対処できないケース

メソッドの mut 宣言だけでは、以下のケースにおいて危険である。

  • allmut なメンバを変更するメソッドは mut 宣言が不要なため、 mut 宣言していなくても、実質的に mutable な動作をするケースがある。
  • モジュールの公開関数からモジュール内の大域変数の変更が可能であり、 かつ関数には mut 宣言がないため安全かどうかの区別できない
  • form の実行において、そのフォームが mutable な処理かどうか区別できない。

LuneScript では、これらについて論理的に対応する方法を考える。

非同期処理の実現方法

go へのトランスコンパイル時は非同期処理をサポートするが、 一方 Lua へトランスコンパイル時は非同期処理をサポートしない。

つまり、非同期処理として書いたものを、 同期処理として動かしても矛盾のない書き方をする必要がある。

これに関しては、非同期処理をサポートしない場合は 「非同期処理を開始する API」実行時に、同期処理として実行することで対応する。

非同期処理インタフェースの実装

ここの情報は検討中

非同期処理は、クラスのメソッドを非同期で処理することで実現する。 このクラスは、__Runner インタフェースを実装する必要がある。

また、__Runner インタフェースを実装するクラスは、以下を制限する。

  • 引数は、全て immutable 型のオブジェクトでなければならない。 これにより、そのクラス内から競合する mutable アクセスがないことを保証する。

    • __init() メソッド
    • pub メソッド
    • ただし、引数のオブジェクトのクラスのメンバが全て immutable 型の場合は、 その引数自体は immutable でなくても良い。
    • 引数の型が型パラメータの場合、条件を満しているとして処理する。
  • メソッドからコールする外部モジュールの関数、メソッドは、次の条件を満さなければならない

    • 大域変数、あるいはクロージャの変数に影響を与えてはならない。
    • allmut への更新がない。

上記制限は、 __Runner インタフェースを実装するクラスの super クラス、 sub クラスも同様に制限される。

上記制限を満すかどうかを確認するため、以下の制御を追加する。

  • __async 宣言を追加する。

    • __async 宣言された関数、メソッドは以下の制限に従う。

      • mutable 型を格納する大域変数、あるいはクロージャの変数にアクセスしない。
      • allmut 型のシンボルの参照がない
      • __noasync な関数をコールしてはならない。
  • _lune_control に default_async_func を追加する。

    • default_async_func が宣言されたモジュールの関数は、 デフォルトで __async 宣言が付加される。
    • __async でない関数は、 __noasync 宣言する必要がある。
    • メソッドは対象外
  • _lune_control に control_default_async_all を追加する。

    • default_async_func が宣言されたモジュールの関数は、 全ての関数、メソッドにおいて、async 宣言がデフォルトで付加される。
  • __noasync 宣言を追加する。

    • __async 宣言とは逆の働きをする。
    • default_async_func が宣言されていないモジュールの関数は、 デフォルトで __noasync が付加される。
  • _lune_control に default_async_this_class を追加する

    • クラスの body 先頭に default_async_this_class を宣言することで、 そのクラス内は control_default_async_all と同じ効果が得られる。
  • _lune_control に default_noasync_this_class を追加する

    • クラスの body 先頭に default_async_this_class を宣言することで、 そのクラス内は control_default_async_all とは逆に、 デフォルトが __noasync 宣言になる。