Принудительное применение безопасного времени компиляции для ссылочных типов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.

Существуют две причины, 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>. В результате правила безопасности, описанные здесь, должны рассматриваться как применимые ко всему диапазону типов, подобных ref.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 позволяет структуре использовать поля экземпляров, имеющие тип ref, и будет также выполнять все требования типов, соответствующих типу 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. Если атрибут недоступен, компилятор создаст внутренний объект, аналогичный другим атрибутам Embedded-On-Demand, таким как 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>
    {
       // . . . .
    }

Примечание. не следует делать это, чтобы любое использование типов, схожих с ref, в старых компиляторах не 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 атрибут или в типе, похожем на тип ref, мы не будем выбирать, Кроме создания предопределенного атрибута, так как 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 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

Мы сопоставлены с каждым выражением во время компиляции понятие области, в которой выражение может переходить в escape-последовательность.We associate with each expression at compile-time the concept of what scope that expression is permitted to escape to, "safe-to-escape". Аналогичным образом для каждого lvalue мы поддерживаем концепцию, в которой разрешается обращаться к области, на которую можно переходить, в «ref-Сейф-to-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". Для данного выражения lvalue они могут отличаться.For a given lvalue expression, these may be different.

Они аналогичны параметру «безопасного возврата» функции «локальные переменные», но более детализированы.These are analogous to the "safe to return" of the ref locals feature, but it is more fine-grained. Если в выражении «Сейф-возврат» записывается только то, что может заключаться в escape-последовательности (или нет), то можно заключить его в неэкранированную область, в которую она может поменяться (в какой области он может не выходить за пределы).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. При присвоении значения в выражении E1 с областью действия S1 в выражении (lvalue) E2 с областью безопасности "безошибочная escape-последовательность 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. По конструкции две области 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.

В то время, когда это достаточно, для анализа необходимо обеспечить поддержку только двух областей — внешних по отношению к методу, и области верхнего уровня метода.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. Однако правила могут поддерживать более двух уровней области.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.

Ссылочная безопасность в escape-последовательностиref-safe-to-escape

Режим ref-to-escape является областью, включающей в себя выражение lvalue, с помощью которого можно обеспечить безопасность ссылки на lvalue для перехода на него.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. Если эта область является всем методом, то мы говорим, что ссылка на lvalue является надежной для возврата из метода.If that scope is the entire method, we say that a ref to the lvalue is safe to return from the method.

безопасность в escape-последовательностиsafe-to-escape

Безопасность в 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

Значение lvalue, обозначающее формальный параметр, — это защищенная ссылка на escape-последовательность (по ссылке), как показано ниже.An lvalue designating a formal parameter is ref-safe-to-escape (by reference) as follows:

  • Если параметр является ref out in параметром, или, то он является типобезопасным для переключения из всего метода (например, 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 параметром типа структуры, он является типобезопасным в escape-последовательности для области верхнего уровня метода (но не из самого метода). Пример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
  • В противном случае параметр является параметром значения и является типобезопасным в escape-последовательности на область верхнего уровня метода (но не из самого метода).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).

Выражение, которое является значением rvalue, обозначающим использование формального параметра, может быть защищено в escape-последовательности (по значению) от всего метода (например, 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

Значение lvalue, обозначающее локальную переменную, является типобезопасным в escape-последовательность (по ссылке) следующим образом:An lvalue designating a local variable is ref-safe-to-escape (by reference) as follows:

  • Если переменная является ref переменной, то ее выражение с модификатором ref -un-escape берется из выражения с модификатором ref-to-escape для его инициализации. в противном случае — значение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.

Выражение, которое является значением rvalue, обозначающим использование локальной переменной, может быть защищено в escape-последовательности (по значению) следующим образом: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 цикла, то область действия " защищено в escape-последовательность " переменной будет такой же, как у безопасного элемента в escape-последовательности 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. Область действия защищенной в escape-последовательности переменной аналогична защищенному в escape-последовательности для его инициализатора.The variable's safe-to-escape scope is the same as the safe-to-escape of its initializer.

Ссылка на полеField reference

Lvalue, обозначающий ссылку на поле, e.F является " типобезопасным" на escape-последовательность (по ссылке) следующим образом:An lvalue designating a reference to a field, e.F, is ref-safe-to-escape (by reference) as follows:

  • Если e имеет ссылочный тип, он является типобезопасным для переключения из всего метода. в противном случае — значение.If e is of a reference type, it is ref-safe-to-escape from the entire method; otherwise
  • Если e имеет тип значения, его строгое Преобразование в escape-последовательность происходит из-за нестрогой ссылки на escape -последовательность e .If e is of a value type, its ref-safe-to-escape is taken from the ref-safe-to-escape of e.

Значение rvalue, обозначающее ссылку на поле, e.F имеет область безопасности в escape-последовательности , совпадающую с защищенной escape-символом 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.

Для оператора, который возвращает rvalue, например e1 + e2 или c ? e1 : e2 , является самой короткой областью между безвозвратной escape -признаком операндов оператора.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. Как следствие, для унарного оператора, который возвращает rvalue, например, защищенное +e в escape-последовательность , результат — это Сейф в escape-последовательности операнда.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.

Для оператора, который возвращает lvalue, например c ? ref e1 : ref e2For an operator that yields an lvalue, such as c ? ref e1 : ref e2

  • последовательность в escape-последовательности для результата является самой короткой областью между оператором ref-un- escape для операндов оператора.the ref-safe-to-escape of the result is the narrowest scope among the ref-safe-to-escape of the operands of the operator.
  • защищенные в escape-последовательность операнды должны быть согласованы, а это защищенное в escape -последовательность итоговое значение lvalue.the safe-to-escape of the operands must agree, and that is the safe-to-escape of the resulting lvalue.

Вызов методаMethod invocation

Значение lvalue, полученное в результате вызова метода, возвращающего ссылку, e1.M(e2, ...) является незащищенным в escape-последовательности на наименьшее из следующих областей: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
  • типобезопасные и escape-последовательности для выражений всех ref аргументов и out (за исключением получателя);the ref-safe-to-escape of all ref and out argument expressions (excluding the receiver)
  • Для каждого in параметра метода, если имеется соответствующее выражение, которое является левосторонним значением, его Ссылочная безопасность в escape-последовательности, в противном случае — Ближайшая включающая область.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
  • Сейф в escape-последовательности всех выражений аргументов (включая получателя)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];

илиor

return ref M(sp, 0);

Значение rvalue, полученное в результате вызова метода 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
  • Сейф в escape-последовательности всех выражений аргументов (включая получателя)the safe-to-escape of all argument expressions (including the receiver)

RvalueAn Rvalue

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. Он также согласуется с (и, возможно, субсумес) нашей обработкой аргументов, соответствующих 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 — это rvalue, который является защищенным в escape-последовательности до области верхнего уровня метода (но не из самого метода).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.

В дополнение к режиму " защищено-escape " не превышает наименьшей из защищенных escape-последовательности всех аргументов или операндов выражений инициализатора объекта, рекурсивно, если имеется инициализатор.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.

Конструктор spanSpan 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 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 типа не могут быть аргументом в методе итератора или async методе.Neither a ref parameter nor a parameter of a ref struct type may be an argument on an iterator method or an async method.

  • Ни Локальная ссылка, ни локаль ref struct типа не могут находиться в области в точке yield return оператора или await выражения.Neither 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 struct .A 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.ValueType .There 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 struct типа.No 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 ref e1 = ref e2 параметру ref- un-Escape e2 должен быть по крайней мере такой же, как и для параметра ref-WITH -escape 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 параметр ref-WITH- return ref e1 escape должен быть типобезопасным в escape-последовательность e1 для всего метода.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 защищенный escape-символ e1 должен быть защищен в escape- последовательности всего метода.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 типом, в качестве значения параметра " защищено на escape-преобразование" 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 типа (включая получателя) с защищенным escape-символом E1, ни один из аргументов (включая получателя) может быть более узким в escape-последовательности , чем E1.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; 
}

Ограничение на приемник является обязательным, так как хотя ни одно из его содержимого не является типобезопасным, для него могут храниться указанные значения.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); 
}

Структура эта escape-последовательностьStruct 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 с именем s).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. В частности, это не защищенная ссылка на escape-последовательность, а другие параметры: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 которых получатель является rvalue.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-to-Escape, то возврат 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 является типобезопасным — escape-символом, 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.

Именно поэтому в 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

Длина одного диапазона <T> над значениями refLength one Span<T> over ref values

Несмотря на то, что на сегодняшний день это будет выгодно, в некоторых случаях может оказаться полезным создание длины одного 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.