参照のような型の安全性のコンパイル時間の強制Compile time enforcement of safety for ref-like types

はじめにIntroduction

やなどの型を処理するときに安全性規則を追加する主な理由は、その Span<T> ReadOnlySpan<T> ような型は実行スタックに限定される必要があるためです。The main reason for the additional safety rules when dealing with types like Span<T> and ReadOnlySpan<T> is that such types must be confined to the execution stack.

2つの理由があり Span<T> 、類似する型はスタックのみの型である必要があります。There are two reasons why Span<T> and similar types must be a stack-only types.

  1. Span<T> は、意味的には参照と範囲を含む構造体です (ref T data, int length)Span<T> is semantically a struct containing a reference and a range - (ref T data, int length). 実際の実装に関係なく、このような構造体への書き込みはアトミックではありません。Regardless of actual implementation, writes to such struct would not be atomic. このような構造体の同時実行の "分裂" によって length 、が一致しないことが原因で、 data 範囲外のアクセスやタイプセーフ違反が発生し、最終的には、"安全な" コードで GC ヒープの破損が発生する可能性があります。Concurrent "tearing" of such struct would lead to the possibility of length not matching the data, causing out-of-range accesses and type-safety violations, which ultimately could result in GC heap corruption in seemingly "safe" code.
  2. の一部の実装 Span<T> では、そのフィールドのいずれかにマネージポインターが含まれています。Some implementations of Span<T> literally contain a managed pointer in one of its fields. マネージポインターは、ヒープオブジェクトのフィールドとしてはサポートされていません。また、GC ヒープにマネージポインターを配置するために管理するコードは、通常、JIT 時にクラッシュします。Managed pointers are not supported as fields of heap objects and code that manages to put a managed pointer on the GC heap typically crashes at JIT time.

のインスタンス Span<T> が実行スタックにのみ存在するように制約されている場合、上記のすべての問題は緩和されます。All the above problems would be alleviated if instances of Span<T> are constrained to exist only on the execution stack.

合成によって追加の問題が発生します。An additional problem arises due to composition. 通常は、とのインスタンスを埋め込む複雑なデータ型を作成することをお勧めし Span<T> ReadOnlySpan<T> ます。It would be generally desirable to build more complex data types that would embed Span<T> and ReadOnlySpan<T> instances. このような複合型は構造体である必要があり、のすべての危険と要件を共有し Span<T> ます。Such composite types would have to be structs and would share all the hazards and requirements of Span<T>. そのため、ここで説明する安全性規則は、 参照のような型 の範囲全体に適用できるものとして表示する必要があります。As a result the safety rules described here should be viewed as applicable to the whole range of ref-like types.

ドラフト言語仕様は、ref に似た型の値がスタックでのみ発生するようにすることを目的としています。The draft language specification is intended to ensure that values of a ref-like type occur only on the stack.

ソースコードの一般化された ref-likeGeneralized ref-like types in source code

ref-like 構造体は、修飾子を使用してソースコードで明示的にマークされ ref ます。ref-like structs are explicitly marked in the source code using ref modifier:

ref struct TwoSpans<T>
{
    // can have ref-like instance fields
    public Span<T> first;
    public Span<T> second;
} 

// error: arrays of ref-like types are not allowed. 
TwoSpans<T>[] arr = null;

構造体を ref like として指定すると、構造体には、参照に似たインスタンスフィールドを設定できます。また、構造体に対しても、ref に似た型のすべての要件が適用されます。Designating a struct as ref-like will allow the struct to have ref-like instance fields and will also make all the requirements of ref-like types applicable to the struct.

メタデータ表現または参照のような構造体Metadata representation or ref-like structs

Ref に似た構造体は、 System.runtime.compilerservices 属性 属性でマークされます。Ref-like structs will be marked with System.Runtime.CompilerServices.IsRefLikeAttribute attribute.

属性は、などの共通の基本ライブラリに追加され mscorlib ます。The attribute will be added to common base libraries such as mscorlib. 属性が使用できない場合、コンパイラは、などの他の埋め込み要求属性と同様に内部的なを生成し IsReadOnlyAttribute ます。In a case if the attribute is not available, compiler will generate an internal one similarly to other embedded-on-demand attributes such as IsReadOnlyAttribute.

追加の手段として、コンパイラで参照に似た構造体が使用されないようにします (この機能が実装される前に C# コンパイラが含まれています)。An additional measure will be taken to prevent the use of ref-like structs in compilers not familiar with the safety rules (this includes C# compilers prior to the one in which this feature is implemented).

サービスを使用せずに古いコンパイラで動作するその他の適切な代替手段がない場合、 Obsolete 既知の文字列を持つ属性が、すべての参照に似た構造体に追加されます。Having no other good alternatives that work in old compilers without servicing, an Obsolete attribute with a known string will be added to all ref-like structs. Ref に似た型の使用方法を認識しているコンパイラは、この特定の形式を無視 Obsolete します。Compilers that know how to use ref-like types will ignore this particular form of Obsolete.

一般的なメタデータ表現は次のとおりです。A typical metadata representation:

    [IsRefLike]
    [Obsolete("Types with embedded references are not supported in this version of your compiler.")]
    public struct TwoSpans<T>
    {
       // . . . .
    }

注: 以前のコンパイラで参照のような型を使用すると、100% が失敗するようにするための目標ではありません。NOTE: it is not the goal to make it so that any use of ref-like types on old compilers fails 100%. これは、実現するのは困難であり、厳密には必要ありません。That is hard to achieve and is not strictly necessary. たとえば、動的コードを使用してを回避する方法や、リフレクションを使用して参照に似た型の配列を作成する方法が常にあり Obsolete ます。For example there would always be a way to get around the Obsolete using dynamic code or, for example, creating an array of ref-like types through reflection.

特に、ユーザーが参照のような型に属性または属性を実際に配置する必要がある場合 Obsolete Deprecated 、属性を Obsolete 複数回適用することはできないため、事前に定義されたもの以外は選択できません。In particular, if user wants to actually put an Obsolete or Deprecated attribute on a ref-like type, we will have no choice other than not emitting the predefined one since Obsolete attribute cannot be applied more than once..

次に例を示します。Examples:

SpanLikeType M1(ref SpanLikeType x, Span<byte> y)
{
    // this is all valid, unconcerned with stack-referring stuff
    var local = new SpanLikeType(y);
    x = local;
    return x;
}

void Test1(ref SpanLikeType param1, Span<byte> param2)
{
    Span<byte> stackReferring1 = stackalloc byte[10];
    var stackReferring2 = new SpanLikeType(stackReferring1);

    // this is allowed
    stackReferring2 = M1(ref stackReferring2, stackReferring1);

    // this is NOT allowed
    stackReferring2 = M1(ref param1, stackReferring1);

    // this is NOT allowed
    param1 = M1(ref stackReferring2, stackReferring1);

    // this is NOT allowed
    param2 = stackReferring1.Slice(10);

    // this is allowed
    param1 = new SpanLikeType(param2);

    // this is allowed
    stackReferring2 = param1;
}

ref SpanLikeType M2(ref SpanLikeType x)
{
    return ref x;
}

ref SpanLikeType Test2(ref SpanLikeType param1, Span<byte> param2)
{
    Span<byte> stackReferring1 = stackalloc byte[10];
    var stackReferring2 = new SpanLikeType(stackReferring1);

    ref var stackReferring3 = M2(ref stackReferring2);

    // this is allowed
    stackReferring3 = M1(ref stackReferring2, stackReferring1);

    // this is allowed
    M2(ref stackReferring3) = stackReferring2;

    // this is NOT allowed
    M1(ref param1) = stackReferring2;

    // this is NOT allowed
    param1 = stackReferring3;

    // this is NOT allowed
    return ref stackReferring3;

    // this is allowed
    return ref param1;
}


ドラフト言語の仕様Draft language specification

以下では、 ref struct これらの型の値がスタックでのみ発生するようにするための、参照に似た型の安全規則のセットについて説明します。Below we describe a set of safety rules for ref-like types (ref structs) to ensure that values of these types occur only on the stack. 参照渡しでローカルに渡すことができない場合は、より単純な安全性規則のセットを使用できます。A different, simpler set of safety rules would be possible if locals cannot be passed by reference. この仕様では、ref ローカルの安全な再割り当ても許可されます。This specification would also permit the safe reassignment of ref locals.

概要Overview

コンパイル時には、式のエスケープが許可されているスコープの概念である "安全な" エスケープ "に関連付けられています。We associate with each expression at compile-time the concept of what scope that expression is permitted to escape to, "safe-to-escape". 同様に、各左辺値については、参照されているスコープの概念は "ref-safe-escape" になります。Similarly, for each lvalue we maintain a concept of what scope a reference to it is permitted to escape to, "ref-safe-to-escape". 指定された左辺値式では、これらは異なる場合があります。For a given lvalue expression, these may be different.

これらは、ref ローカル機能の "安全に戻る" に似ていますが、さらにきめ細かです。These are analogous to the "safe to return" of the ref locals feature, but it is more fine-grained. 式の "セーフツーリターン" によって、外側のメソッドが完全にエスケープされるかどうかだけが記録されます。これは、そのスコープがエスケープされる可能性がある (スコープはエスケープされない場合があります)。Where the "safe-to-return" of an expression records only whether (or not) it may escape the enclosing method as a whole, the safe-to-escape records which scope it may escape to (which scope it may not escape beyond). 基本的な安全性メカニズムは、次のように適用されます。The basic safety mechanism is enforced as follows. 安全-エスケープスコープ S1 を持つ式 E1 から (左辺値) 式 E2 を使用して、安全な値を持つ S2 に割り当てられている場合、S2 が S1 よりも広いスコープの場合、エラーになります。Given an assignment from an expression E1 with a safe-to-escape scope S1, to an (lvalue) expression E2 with safe-to-escape scope S2, it is an error if S2 is a wider scope than S1. 構築上、2つのスコープ S1 と S2 は、式を囲むスコープから常に安全に戻ることができるため、入れ子関係にあります。By construction, the two scopes S1 and S2 are in a nesting relationship, because a legal expression is always safe-to-return from some scope enclosing the expression.

そのためには、分析のために、メソッドの外部のスコープとメソッドの最上位スコープの2つのスコープのみをサポートするために、十分な時間を確保します。For the time being it is sufficient, for the purpose of the analysis, to support just two scopes - external to the method, and top-level scope of the method. これは、内部スコープを持つ参照のような値を作成できず、ref ローカルが再割り当てをサポートしていないためです。That is because ref-like values with inner scopes cannot be created and ref locals do not support re-assignment. ただし、ルールでは、3つ以上のスコープレベルをサポートできます。The rules, however, can support more than two scope levels.

式の 信頼できる 状態の状態を計算するための正確な規則と、式の正規表現を制御する規則に従います。The precise rules for computing the safe-to-return status of an expression, and the rules governing the legality of expressions, follow.

参照セーフ-エスケープref-safe-to-escape

参照セーフツーエスケープは、左辺値式を囲む範囲であり、左辺値がエスケープされるまでの参照が安全です。The ref-safe-to-escape is a scope, enclosing an lvalue expression, to which it is safe for a ref to the lvalue to escape to. このスコープがメソッド全体である場合は、左辺値への参照がメソッドから 安全に戻る ことができます。If that scope is the entire method, we say that a ref to the lvalue is safe to return from the method.

安全-エスケープsafe-to-escape

セーフツーエスケープは、式を囲むスコープで、値がエスケープされても安全です。The safe-to-escape is a scope, enclosing an expression, to which it is safe for the value to escape to. このスコープがメソッド全体である場合は、値がメソッドから 返さ れることが安全であると言います。If that scope is the entire method, we say that the value is safe to return from the method.

型が型ではない式 ref struct は、外側のメソッド全体から 安全に戻る ことができます。An expression whose type is not a ref struct type is safe-to-return from the entire enclosing method. それ以外の場合は、以下の規則を参照してください。Otherwise we refer to the rules below.

パラメーターParameters

仮パラメーターを指定する左辺値は、次のように、参照によって (参照によって) 参照が 安全 です。An lvalue designating a formal parameter is ref-safe-to-escape (by reference) as follows:

  • パラメーターが、、またはパラメーターの場合は、 ref out in メソッド全体 (ステートメントなど) からの ref セーフツーエスケープ です return ref 。それ以外の場合は、If the parameter is a ref, out, or in parameter, it is ref-safe-to-escape from the entire method (e.g. by a return ref statement); otherwise
  • パラメーターが this 構造体型のパラメーターである場合は、メソッドの最上位スコープ (ただし、メソッド自体からではない) に対して、 参照セーフからエスケープ されます。 サンプルIf the parameter is the this parameter of a struct type, it is ref-safe-to-escape to the top-level scope of the method (but not from the entire method itself); Sample
  • それ以外の場合、パラメーターは値パラメーターであり、(メソッド自体からではなく) メソッドの最上位のスコープに対して 参照セーフからエスケープ されます。Otherwise the parameter is a value parameter, and it is ref-safe-to-escape to the top-level scope of the method (but not from the method itself).

仮パラメーターの使用を指定する右辺値である式は、メソッド全体 (たとえば、ステートメント) から (値によって) 安全にエスケープ され return ます。An expression that is an rvalue designating the use of a formal parameter is safe-to-escape (by value) from the entire method (e.g. by a return statement). これは、パラメーターにも適用さ this れます。This applies to the this parameter as well.

ローカルLocals

ローカル変数を指定する左辺値は、次のように、参照によって (参照によって) 参照が 安全 です。An lvalue designating a local variable is ref-safe-to-escape (by reference) as follows:

  • 変数が ref 変数である場合、その変数の ref と escape は、初期化式の ref セーフツーエスケープ から取得されます。それ以外の場合は、If the variable is a ref variable, then its ref-safe-to-escape is taken from the ref-safe-to-escape of its initializing expression; otherwise
  • 変数は、宣言されたスコープを 参照セーフでエスケープ します。The variable is ref-safe-to-escape the scope in which it was declared.

ローカル変数の使用を指定する右辺値である式は、次のように 安全にエスケープでき ます。An expression that is an rvalue designating the use of a local variable is safe-to-escape (by value) as follows:

  • ただし、上記の一般的なルールでは、型が型でないローカルは、 ref struct 外側のメソッド全体から 安全に戻り ます。But the general rule above, a local whose type is not a ref struct type is safe-to-return from the entire enclosing method.
  • 変数がループの反復変数である場合、 foreach 変数の セーフツーエスケープ のスコープは、ループの式の 安全なエスケープ と同じになり foreach ます。If the variable is an iteration variable of a foreach loop, then the variable's safe-to-escape scope is the same as the safe-to-escape of the foreach loop's expression.
  • 型のローカルの ref struct と、宣言の時点で初期化されていない型は、外側のメソッド全体から 安全に戻る ことができます。A local of ref struct type and uninitialized at the point of declaration is safe-to-return from the entire enclosing method.
  • それ以外の場合、変数の型は ref struct 型であり、変数の宣言には初期化子が必要です。Otherwise the variable's type is a ref struct type, and the variable's declaration requires an initializer. 変数の セーフツーエスケープ のスコープは、その初期化子の 安全なエスケープ と同じです。The variable's safe-to-escape scope is the same as the safe-to-escape of its initializer.

フィールド参照Field reference

フィールドへの参照を指定する左辺値 e.F は、次のよう 、参照によって (参照によって) 参照が安全です。An lvalue designating a reference to a field, e.F, is ref-safe-to-escape (by reference) as follows:

  • eが参照型の場合は、メソッド全体からのref セーフツーエスケープです。それ以外の場合は、If e is of a reference type, it is ref-safe-to-escape from the entire method; otherwise
  • eが値型の場合、の ref セーフツーエスケープは、のref セーフツーエスケープから取得され e ます。If e is of a value type, its ref-safe-to-escape is taken from the ref-safe-to-escape of e.

フィールドへの参照を指定する右辺値には、 e.Fセーフツーエスケープと同じである、安全にエスケープできるスコープがあり e ます。An rvalue designating a reference to a field, e.F, has a safe-to-escape scope that is the same as the safe-to-escape of e.

を含む演算子 ?:Operators including ?:

ユーザー定義の演算子のアプリケーションは、メソッドの呼び出しとして扱われます。The application of a user-defined operator is treated as a method invocation.

またはのような右辺値を生成する演算子の場合、結果の安全な操作の範囲は、 e1 + e2 c ? e1 : e2 演算子のオペランドのセーフツーエスケープの範囲において最も狭い範囲になります。 safe-to-escapeFor an operator that yields an rvalue, such as e1 + e2 or c ? e1 : e2, the safe-to-escape of the result is the narrowest scope among the safe-to-escape of the operands of the operator. 結果として、などの単項演算子が右辺値を生成する場合、 +e 結果の 安全なエスケープ は、オペランドの 安全なエスケープ です。As a consequence, for a unary operator that yields an rvalue, such as +e, the safe-to-escape of the result is the safe-to-escape of the operand.

などの左辺値を生成する演算子の場合 c ? ref e1 : ref e2For an operator that yields an lvalue, such as c ? ref e1 : ref e2

  • 結果の ref セーフツーエスケープ は、演算子のオペランドの ref セーフツーエスケープ の範囲の中で最も狭いスコープです。the ref-safe-to-escape of the result is the narrowest scope among the ref-safe-to-escape of the operands of the operator.
  • オペランドが 安全にエスケープ される必要があります。これは、結果として得られる左辺値の安全な エスケープ です。the safe-to-escape of the operands must agree, and that is the safe-to-escape of the resulting lvalue.

メソッドの呼び出しMethod invocation

参照を返すメソッド呼び出しの結果として得られる左辺値は、次のスコープのうち最小のものを e1.M(e2, ...) 参照して 安全にエスケープし ます。An lvalue resulting from a ref-returning method invocation e1.M(e2, ...) is ref-safe-to-escape the smallest of the following scopes:

  • 外側のメソッド全体。The entire enclosing method
  • すべてのおよび引数式の ref セーフ ツーエスケープ ref out (受信側を除く)the ref-safe-to-escape of all ref and out argument expressions (excluding the receiver)
  • メソッドの各パラメーターについて、左辺値が左辺値である場合は、 in それ以外の場合 は、 最も近い外側のスコープFor each in parameter of the method, if there is a corresponding expression that is an lvalue, its ref-safe-to-escape, otherwise the nearest enclosing scope
  • すべての引数式 (受信側を含む) の 安全なエスケープthe safe-to-escape of all argument expressions (including the receiver)

注: 次のようなコードを処理するには、最後の行頭文字が必要です。Note: the last bullet is necessary to handle code such as

var sp = new Span(...)
return ref sp[0];

oror

return ref M(sp, 0);

メソッド呼び出しの結果として得られる右辺値 e1.M(e2, ...) は、次の範囲の最小値から 安全にエスケープでき ます。An rvalue resulting from a method invocation e1.M(e2, ...) is safe-to-escape from the smallest of the following scopes:

  • 外側のメソッド全体。The entire enclosing method
  • すべての引数式 (受信側を含む) の 安全なエスケープthe safe-to-escape of all argument expressions (including the receiver)

右辺値An Rvalue

右辺値は、最も近い外側のスコープから、 参照セーフからエスケープ されます。An rvalue is ref-safe-to-escape from the nearest enclosing scope. これは、などの呼び出しで、が型の場合に発生し M(ref d.Length) d dynamic ます。This occurs for example in an invocation such as M(ref d.Length) where d is of type dynamic. また、パラメーターに対応する引数の処理 (および subsumes) にも一貫性があり in ます。It is also consistent with (and perhaps subsumes) our handling of arguments corresponding to in parameters.

プロパティの呼び出しProperty invocations

プロパティ呼び出し (またはのいずれか) は、 get set 上記の規則によって基になるメソッドのメソッド呼び出しとして扱われます。A property invocation (either get or set) it treated as a method invocation of the underlying method by the above rules.

stackalloc

Stackalloc 式は、メソッドの最上位スコープに対して 安全にエスケープ できる右辺値です (ただし、メソッド自体からではありません)。A stackalloc expression is an rvalue that is safe-to-escape to the top-level scope of the method (but not from the entire method itself).

コンストラクターの呼び出しConstructor invocations

newコンストラクターを呼び出す式は、構築される型を返すと見なされるメソッド呼び出しと同じ規則に従います。A new expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed.

さらに、オブジェクト初期化子式のすべての引数またはオペランドの、セーフツーエスケープの最小値を超えないようにします。これは、初期化子が存在する場合に再帰的に行われます。In addition safe-to-escape is no wider than the smallest of the safe-to-escape of all arguments/operands of the object initializer expressions, recursively, if initializer is present.

Span コンストラクターSpan constructor

この言語は、 Span<T> 次の形式のコンストラクターがないことに依存しています。The language relies on Span<T> not having a constructor of the following form:

void Example(ref int x)
{
    // Create a span of length one
    var span = new Span<int>(ref x); 
}

このようなコンストラクターにより、フィールド Span<T> と区別できないフィールドとして使用され ref ます。Such a constructor makes Span<T> which are used as fields indistinguishable from a ref field. このドキュメントで説明されている安全性規則は、 ref C# または .net の有効な構成要素ではないフィールドによって異なります。The safety rules described in this document depend on ref fields not being a valid construct in C# or .NET.

defaultdefault expressions

default式は、外側のメソッド全体から安全にエスケープできます。A default expression is safe-to-escape from the entire enclosing method.

言語の制約Language Constraints

refローカル変数と型の変数が、現在は動作してい ref struct ないスタックメモリまたは変数を参照していないことを確認したいと考えています。We wish to ensure that no ref local variable, and no variable of ref struct type, refers to stack memory or variables that are no longer alive. そのため、次の言語の制約があります。We therefore have the following language constraints:

  • Ref パラメーター、ref ローカル、または型のローカルパラメーターを ref struct ラムダまたはローカル関数に変換することはできません。Neither a ref parameter, nor a ref local, nor a parameter or local of a ref struct type can be lifted into a lambda or local function.

  • Ref パラメーターも型のパラメーターも、 ref struct 反復子メソッドまたはメソッドの引数として使用することはできません asyncNeither a ref parameter nor a parameter of a ref struct type may be an argument on an iterator method or an async method.

  • Ref ローカルと型のローカルのどちらも、 ref struct yield return ステートメントまたは式の時点でスコープ内に存在することはできません awaitNeither a ref local, nor a local of a ref struct type may be in scope at the point of a yield return statement or an await expression.

  • 型を ref struct 型引数として使用したり、タプル型の要素型として使用したりすることはできません。A ref struct type may not be used as a type argument, or as an element type in a tuple type.

  • 型は、 ref struct 別のインスタンスフィールドの宣言型であることを除いて、宣言された型のフィールドにすることはできません ref structA ref struct type may not be the declared type of a field, except that it may be the declared type of an instance field of another ref struct.

  • ref struct型を配列の要素型にすることはできません。A ref struct type may not be the element type of an array.

  • 型の値を ref struct ボックス化することはできません。A value of a ref struct type may not be boxed:

    • 型から ref struct 型または型への変換は行われません object System.ValueTypeThere is no conversion from a ref struct type to the type object or the type System.ValueType.
    • ref structインターフェイスを実装するために型を宣言することはできませんA ref struct type may not be declared to implement any interface
    • objectまたはで宣言 System.ValueType されているが、型でオーバーライドされていないインスタンスメソッドは、 ref struct その型のレシーバーで呼び出すことができません ref structNo instance method declared in object or in System.ValueType but not overridden in a ref struct type may be called with a receiver of that ref struct type.
    • ref structメソッドをデリゲート型に変換することによって、型のインスタンスメソッドをキャプチャすることはできません。No instance method of a ref struct type may be captured by method conversion to a delegate type.
  • 参照の再割り当ての場合 ref e1 = ref e2 、の ref セーフツーエスケープ は、 e2ref セーフツーエスケープ と同じ範囲内である必要があり e1 ます。For a ref reassignment ref e1 = ref e2, the ref-safe-to-escape of e2 must be at least as wide a scope as the ref-safe-to-escape of e1.

  • Ref return ステートメントの場合 return ref e1 、の ref セーフツーエスケープ は、 e1 メソッド全体からの ref セーフツーエスケープ である必要があります。For a ref return statement return ref e1, the ref-safe-to-escape of e1 must be ref-safe-to-escape from the entire method. (TODO: e1 メソッド全体から 安全にエスケープ する必要がある規則や、その冗長な規則も必要ですか)。(TODO: Do we also need a rule that e1 must be safe-to-escape from the entire method, or is that redundant?)

  • Return ステートメントの場合 return e1 、の セーフツーエスケープ は、 e1 メソッド全体から 安全にエスケープ される必要があります。For a return statement return e1, the safe-to-escape of e1 must be safe-to-escape from the entire method.

  • 割り当ての e1 = e2 場合、の型が型である場合、の e1 セーフツー ref struct エスケープ は、 e2 の安全な エスケープ として、少なくとも範囲内である必要があり e1 ます。For an assignment e1 = e2, if the type of e1 is a ref struct type, then the safe-to-escape of e2 must be at least as wide a scope as the safe-to-escape of e1.

  • メソッドの呼び出しで ref out 、型のまたは引数 ref struct (レシーバーを含む) がある場合、セーフツーエスケープE1 を使用した場合、引数 (レシーバーを含む) は、e1 よりsafe-to-escapeも幅が狭くなることがあります。For a method invocation if there is a ref or out argument of a ref struct type (including the receiver), with safe-to-escape E1, then no argument (including the receiver) may have a narrower safe-to-escape than E1. サンプルSample

  • ローカル関数または匿名関数が、外側のスコープで宣言された型のローカルまたはパラメーターを参照していない可能性があり ref struct ます。A local function or anonymous function may not refer to a local or parameter of ref struct type declared in an enclosing scope.

懸案事項を開く:ref structたとえば、コード内の await 式で型のスタック値をスピルする必要がある場合に、エラーが発生することを許可するルールが必要です。Open Issue: We need some rule that permits us to produce an error when needing to spill a stack value of a ref struct type at an await expression, for example in the code

Foo(new Span<int>(...), await e2);

コメントExplanations

これらの説明とサンプルは、上記の安全性規則の多くが存在する理由を説明するのに役立ちます。These explanations and samples help explain why many of the safety rules above exist

メソッドの引数は一致しなければなりませんMethod Arguments Must Match

受信側を含むというパラメーターがあるメソッドを呼び出す場合 out ref ref struct 、すべてのが ref struct 同じ有効期間を持つ必要があります。When invoking a method where there is an out, ref parameter that is a ref struct including the receiver then all of the ref struct need to have the same lifetime. これが必要なのは、C# では、メソッドのシグネチャで使用可能な情報と、呼び出しサイトの値の有効期間に基づいて、有効期間の安全性に関するすべての決定を行う必要があるためです。This is necessary because C# must make all of its decisions around lifetime safety based on the information available in the signature of the method and the lifetime of the values at the call site.

パラメーターがある場合は ref ref struct 、それらのおそれが内容を交換することができます。When there are ref parameters that are ref struct then there is the possiblity they could swap around their contents. そのため、呼び出しサイトでは、これらの 可能性 のあるすべてのスワップに互換性があることを確認する必要があります。Hence at the call site we must ensure all of these potential swaps are compatible. 言語によって強制されなかった場合は、次のような不適切なコードを使用できます。If the language didn't enforce that then it will allow for bad code like the following.

void M1(ref Span<int> s1)
{
    Span<int> s2 = stackalloc int[1];
    Swap(ref s1, ref s2);
}

void Swap(ref Span<int> x, ref Span<int> y)
{
    // This will effectively assign the stackalloc to the s1 parameter and allow it
    // to escape to the caller of M1
    ref x = ref y; 
}

受信側の制限は、その内容がいずれも ref セーフツーエスケープではないのに、指定された値を格納できるため、受信側の制限が必要です。The restriction on the receiver is necessary because while none of its contents are ref-safe-to-escape it can store the provided values. つまり、有効期間が一致しない場合は、次の方法でタイプセーフホールを作成できます。This means with mismatched lifetimes you could create a type safety hole in the following way:

ref struct S
{
    public Span<int> Span;

    public void Set(Span<int> span)
    {
        Span = span;
    }
}

void Broken(ref S s)
{
    Span<int> span = stackalloc int[1];

    // The result of a stackalloc is now stored in s.Span and escaped to the caller
    // of Broken
    s.Set(span); 
}

Struct This EscapeStruct This Escape

安全性規則に関して this は、インスタンスメンバーの値は、メンバーのパラメーターとしてモデル化されます。When it comes to span safety rules, the this value in an instance member is modeled as a parameter to the member. では、 struct の型 this は実際 ref S には class S (名前付きののメンバーに対して) 単純にです class / structNow for a struct the type of this is actually ref S where in a class it's simply S (for members of a class / struct named S).

thisには、他のパラメーターとは異なるエスケープ規則があり ref ます。Yet this has different escaping rules than other ref parameters. 具体的には、他のパラメーターは次のように、参照セーフツーエスケープではありません。Specifically it is not ref-safe-to-escape while other parameters are:

ref struct S
{ 
    int Field;

    // Illegal because `this` isn't safe to escape as ref
    ref int Get() => ref Field;

    // Legal
    ref int GetParam(ref int p) => ref p;
}

この制限の理由は、実際にメンバーの呼び出しにはほとんどありません structThe reason for this restriction actually has little to do with struct member invocation. 受信側が右辺値であるメンバーのメンバーの呼び出しに関して、いくつかの規則を使用する必要があり struct ます。There are some rules that need to be worked out with respect to member invocation on struct members where the receiver is an rvalue. しかし、これは非常に便利です。But that is very approachable.

この制限の理由は、実際にはインターフェイスの呼び出しに関するものです。The reason for this restriction is actually about interface invocation. 具体的には、次のサンプルをコンパイルする必要があるかどうかについては、次のようになります。Specifically it comes down to whether or not the following sample should or should not compile;

interface I1
{
    ref int Get();
}

ref int Use<T>(T p)
    where T : I1
{
    return ref p.Get();
}

Tがとしてインスタンス化されている場合を考えてみ struct ます。Consider the case where T is instantiated as a struct. thisパラメーターが ref セーフツーエスケープの場合、の戻り値は p.Get スタックを指している可能性があります (具体的には、のインスタンス化された型の内部のフィールドである可能性があり T ます)。If the this parameter is ref-safe-to-escape then the return of p.Get could point to the stack (specifically it could be a field inside of the instantiated type of T). これは、をスタック位置に返す可能性があるため、このサンプルをコンパイルすることを言語が許可できなかったことを意味し ref ます。That means the language could not allow this sample to compile as it could be returning a ref to a stack location. 一方、 this が ref セーフツーエスケープでない場合は p.Get 、がスタックを参照できないため、が安全に返されます。On the other hand if this is not ref-safe-to-escape then p.Get cannot refer to the stack and hence it's safe to return.

このため、内のの escapability ビリティは、 this struct インターフェイスに関しては本当にすべてです。This is why the escapability of this in a struct is really all about interfaces. この機能は正常に実行できますが、トレードオフになっています。It can absolutely be made to work but it has a trade off. インターフェイスの柔軟性を高めるために、最終的に設計はダウンしました。The design eventually came down in favor of making interfaces more flexible.

ただし、今後この問題を緩和する可能性はあります。There is potential for us to relax this in the future though.

将来の注意事項Future Considerations

Ref 値の1スパンの長さ <T>Length one Span<T> over ref values

現時点では有効ではありませんが、値に対して1つのインスタンスの長さを作成することが有益な場合があり Span<T> ます。Though not legal today there are cases where creating a length one Span<T> instance over a value would be beneficial:

void RefExample()
{
    int x = ...;

    // Today creating a length one Span<int> requires a stackalloc and a new 
    // local
    Span<int> span1 = stackalloc [] { x };
    Use(span1);
    x = span1[0]; 

    // Simpler to just allow length one span
    var span2 = new Span<int>(ref x);
    Use(span2);
}

この機能は、より長い長さのインスタンスで許容されるように、 固定サイズのバッファー に制限を設けると、より説得力が高まり Span<T> ます。This feature gets more compelling if we lift the restrictions on fixed sized buffers as it would allow for Span<T> instances of even greater length.

このパスを下げる必要がある場合は、このようなインスタンスが下方向にのみ表示されるようにすることで、このような状況に対応でき Span<T> ます。If there is ever a need to go down this path then the language could accommodate this by ensuring such Span<T> instances were downward facing only. これは、作成されたスコープに対してのみ 安全にエスケープ されたことです。That is they were only ever safe-to-escape to the scope in which they were created. これにより、 ref ref struct の戻り値またはフィールドを使用してメソッドをエスケープする値を、言語で考慮する必要がなくなりました ref structThis ensure the language never had to consider a ref value escaping a method via a ref struct return or field of ref struct. また、この方法でパラメーターをキャプチャするようなコンストラクターを認識するために、さらに変更が必要になる場合もあり ref ます。This would likely also require further changes to recognize such constructors as capturing a ref parameter in this way though.