参照のような型の安全性のコンパイル時間の強制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.
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 oflength
not matching thedata
, causing out-of-range accesses and type-safety violations, which ultimately could result in GC heap corruption in seemingly "safe" code.- の一部の実装
Span<T>
では、そのフィールドのいずれかにマネージポインターが含まれています。Some implementations ofSpan<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-like
型Generalized 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 struct
s) 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 aref
,out
, orin
parameter, it is ref-safe-to-escape from the entire method (e.g. by areturn ref
statement); otherwise - パラメーターが
this
構造体型のパラメーターである場合は、メソッドの最上位スコープ (ただし、メソッド自体からではない) に対して、 参照セーフからエスケープ されます。 サンプルIf the parameter is thethis
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 aref
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 aref struct
type is safe-to-return from the entire enclosing method. - 変数がループの反復変数である場合、
foreach
変数の セーフツーエスケープ のスコープは、ループの式の 安全なエスケープ と同じになりforeach
ます。If the variable is an iteration variable of aforeach
loop, then the variable's safe-to-escape scope is the same as the safe-to-escape of theforeach
loop's expression. - 型のローカルの
ref struct
と、宣言の時点で初期化されていない型は、外側のメソッド全体から 安全に戻る ことができます。A local ofref 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 aref 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 セーフツーエスケープ です。それ以外の場合は、Ife
is of a reference type, it is ref-safe-to-escape from the entire method; otherwisee
が値型の場合、の ref セーフツー エスケープ は、の ref セーフツーエスケープ から取得されe
ます。Ife
is of a value type, its ref-safe-to-escape is taken from the ref-safe-to-escape ofe
.
フィールドへの参照を指定する右辺値には、 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
演算子のオペランドの セーフツーエスケープ の範囲において最も狭い範囲になります。 For 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 e2
For 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 allref
andout
argument expressions (excluding the receiver) - メソッドの各パラメーターについて、左辺値が左辺値である場合は、
in
それ以外の場合 は、 最も近い外側のスコープFor eachin
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.
default
式default
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 aref struct
type can be lifted into a lambda or local function.Ref パラメーターも型のパラメーターも、
ref struct
反復子メソッドまたはメソッドの引数として使用することはできませんasync
。Neither a ref parameter nor a parameter of aref struct
type may be an argument on an iterator method or anasync
method.Ref ローカルと型のローカルのどちらも、
ref struct
yield return
ステートメントまたは式の時点でスコープ内に存在することはできませんawait
。Neither a ref local, nor a local of aref struct
type may be in scope at the point of ayield return
statement or anawait
expression.型を
ref struct
型引数として使用したり、タプル型の要素型として使用したりすることはできません。Aref struct
type may not be used as a type argument, or as an element type in a tuple type.型は、
ref struct
別のインスタンスフィールドの宣言型であることを除いて、宣言された型のフィールドにすることはできませんref struct
。Aref struct
type may not be the declared type of a field, except that it may be the declared type of an instance field of anotherref struct
.ref struct
型を配列の要素型にすることはできません。Aref struct
type may not be the element type of an array.型の値を
ref struct
ボックス化することはできません。A value of aref struct
type may not be boxed:- 型から
ref struct
型または型への変換は行われませんobject
System.ValueType
。There is no conversion from aref struct
type to the typeobject
or the typeSystem.ValueType
. ref struct
インターフェイスを実装するために型を宣言することはできませんAref struct
type may not be declared to implement any interfaceobject
またはで宣言System.ValueType
されているが、型でオーバーライドされていないインスタンスメソッドは、ref struct
その型のレシーバーで呼び出すことができませんref struct
。No instance method declared inobject
or inSystem.ValueType
but not overridden in aref struct
type may be called with a receiver of thatref struct
type.ref struct
メソッドをデリゲート型に変換することによって、型のインスタンスメソッドをキャプチャすることはできません。No instance method of aref struct
type may be captured by method conversion to a delegate type.
- 型から
参照の再割り当ての場合
ref e1 = ref e2
、の ref セーフツーエスケープ は、e2
の ref セーフツーエスケープ と同じ範囲内である必要がありe1
ます。For a ref reassignmentref e1 = ref e2
, the ref-safe-to-escape ofe2
must be at least as wide a scope as the ref-safe-to-escape ofe1
.Ref return ステートメントの場合
return ref e1
、の ref セーフツーエスケープ は、e1
メソッド全体からの ref セーフツーエスケープ である必要があります。For a ref return statementreturn ref e1
, the ref-safe-to-escape ofe1
must be ref-safe-to-escape from the entire method. (TODO:e1
メソッド全体から 安全にエスケープ する必要がある規則や、その冗長な規則も必要ですか)。(TODO: Do we also need a rule thate1
must be safe-to-escape from the entire method, or is that redundant?)Return ステートメントの場合
return e1
、の セーフツーエスケープ は、e1
メソッド全体から 安全にエスケープ される必要があります。For a return statementreturn e1
, the safe-to-escape ofe1
must be safe-to-escape from the entire method.割り当ての
e1 = e2
場合、の型が型である場合、のe1
セーフツーref struct
エスケープ は、e2
の安全な エスケープ として、少なくとも範囲内である必要がありe1
ます。For an assignmente1 = e2
, if the type ofe1
is aref struct
type, then the safe-to-escape ofe2
must be at least as wide a scope as the safe-to-escape ofe1
.メソッドの呼び出しで
ref
out
、型のまたは引数ref struct
(レシーバーを含む) がある場合、セーフツーエスケープ E1 を使用した場合、引数 (レシーバーを含む) は、e1 よりも幅が狭くなることがあります。For a method invocation if there is aref
orout
argument of aref 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 ofref 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 aref struct
type at an await expression, for example in the codeFoo(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 / struct
。Now 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;
}
この制限の理由は、実際にメンバーの呼び出しにはほとんどありません struct
。The 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 struct
。This 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.