跳转到内容

ADR-0038: Disallow Source Initializers for Public State

此内容尚不支持你的语言。

Accepted

Sobakasuのstateには二種類の初期値所有モデルがある。

private stateでは、Sobakasu sourceが初期値を所有する。

state count = 1;

initializerはcompile-time evaluationされ、既存のstate initializer、GlobalInitializer、heap patch semanticsを使用する。

public stateはUdonの.exportによりUdonBehaviourのpublic variableとして公開され、Unity Inspector、serialized UdonBehaviour、ProgramVariable interoperabilityなどの外部側から値を与えられる。

pub state count: i32;

従来の次の宣言は、sourceが実行時初期値を所有するように見える。

pub state count: i32 = 1;

しかし、実際の実行時値にはInspectorに保存されたpublic variableの値が優先されることがあり、Sobakasu sourceは1を保証できない。source initializerとInspector serialized valueの両方が「初期値」に見える二重の所有モデルを避ける必要がある。

ADR-0025はprivate/publicを区別せず、すべてのstate declarationにinitializerを要求していた。本ADRは、そのgrammarとpublic stateにsource initializerが存在するという部分だけをsupersedeする。ADR-0014はADR-0025にsupersede済みであり、.export、.sync、publicとsyncの独立性などの歴史的経緯として参照する。

state declarationを意味論上、次の二種類として扱う。

PrivateStateDeclaration
::= SyncModifier? "state" Identifier TypeClause? "=" Expression ";"
PublicStateDeclaration
::= "pub" SyncModifier? "state" Identifier TypeClause ";"

正規例は次のとおりである。

state count = 1;
state count: i32 = 1;
sync state count = 1;
sync(linear) state value: f32 = 0.0;
pub state count: i32;
pub sync state count: i32;
pub sync(linear) state value: f32;

次はsource initializerを持つため不正である。

pub state count = 1;
pub state count: i32 = 1;
pub sync state count: i32 = 1;
pub sync(linear) state value: f32 = 1.0;

pub state count;も不正とする。public stateにはinitializerがなく、initializerから型を推論できないため、explicit type annotationを必須とする。

禁止されたpublic initializerはParserで式全体をconsumeして専用診断を報告し、Binderでは式をbindまたはconstant evaluationしない。これによりinitializer内部の二次診断を抑え、後続memberのparse recoveryを維持する。

正常なpublic stateは、型、public metadata、sync metadataを持ち、source initializerとsource InitialValueを持たない。IRは従来のstate storageを生成し、UASMは型に応じた既存placeholderを持つdata slotと.exportを生成する。同期指定があれば.syncも生成する。GlobalInitializer heap patchは生成しない。

次の既存仕様は変更しない。

  • private stateのinitializer必須、initializerからの型推論、constant evaluation、InitialValue、GlobalInitializer
  • private synchronized stateとそのinitializer
  • stateが常にmutableであること
  • pubとsyncの独立性、およびprivate synchronized state
  • .export、.sync、public array/aggregate ABI validation、synchronization compatibility validation
  • pub const、const initializer、local let
  • Maybe/Nothing semantics

したがって、sync state count = 1;は引き続き合法である。initializerを禁止する条件はsyncではなく、pubによりInspector/public variable側が値の所有者になることである。

pub state x = 1を維持し、Inspectorが優先すると文書化する

Section titled “pub state x = 1を維持し、Inspectorが優先すると文書化する”

却下する。source initializerが実行時値を保証するように見える問題が残る。

Sobakasu initializerをInspectorより常に優先する

Section titled “Sobakasu initializerをInspectorより常に優先する”

却下する。Inspectorで利用者が設定したserialized public variableをコンパイル時に上書きすることになり、Unity/Udonのpublic variable semanticsと相性が悪い。

source initializerをInspectorのdefault valueとして自動転写する

Section titled “source initializerをInspectorのdefault valueとして自動転写する”

今回は採用しない。再コンパイル時に、過去の自動生成defaultと利用者がInspectorで明示的に変更した値を区別する追加仕様が必要になる。本決定の目的はpublic stateの初期値所有者を一つにすることであり、その複雑性を導入しない。

主目的は実装上の可否ではなく、sourceの意味を明確にすることである。

state x = 1;は「Sobakasu sourceが初期値1を定義する」と読める。pub state x: i32;は「UdonBehaviour/Inspectorから値が提供される公開状態」と読める。初期値の所有境界を構文に表すことで、sourceと実行時の解釈を一致させる。

Parserは構文上禁止されたinitializerの診断とrecovery、Binderは正常なinitializer absence、型、ABI、同期compatibilityの意味解析、IR/UASMは解決済みstate storageのemissionを担当するため、既存の責務分離も維持できる。

  • public stateのsource initializerとInspector valueの競合がなくなる。
  • sourceだけで初期値所有者を判断できる。
  • public stateの型が明示される。
  • Unity/Udonのpublic variable semanticsと一致する。
  • private stateのinitializer semanticsを維持できる。
  • 既存のpub state x = ...;はbreaking changeになる。
  • 既存source、tests、samples、docsの更新が必要になる。
  • public stateではinitializerによる型推論を使用できない。