Tempo de compilação imposição de segurança para tipos semelhantes a refCompile time enforcement of safety for ref-like types

IntroduçãoIntroduction

O principal motivo para as regras de segurança adicionais ao lidar com tipos como Span<T> e ReadOnlySpan<T> é que esses tipos devem ser confinados para a pilha de execução.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.

Há dois motivos pelos quais Span<T> e tipos semelhantes devem ser tipos somente de pilha.There are two reasons why Span<T> and similar types must be a stack-only types.

  1. Span<T> é semanticamente uma estrutura que contém uma referência e um intervalo (ref T data, int length) .Span<T> is semantically a struct containing a reference and a range - (ref T data, int length). Independentemente da implementação real, as gravações em tal struct não seriam atômicas.Regardless of actual implementation, writes to such struct would not be atomic. A "divisão" simultânea dessa estrutura levaria à possibilidade de length não corresponder ao data , causando acessos fora do intervalo e violações de segurança de tipo, o que, por fim, pode resultar em uma corrupção de heap de GC no código "seguro" aparentemente.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. Algumas implementações de Span<T> literalmente contêm um ponteiro gerenciado em um de seus campos.Some implementations of Span<T> literally contain a managed pointer in one of its fields. Não há suporte para ponteiros gerenciados, pois campos de objetos de heap e código que gerencia para colocar um ponteiro gerenciado no heap de GC normalmente falham no momento do 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.

Todos os problemas acima seriam aliviados se as instâncias do Span<T> estiverem restritas apenas na pilha de execução.All the above problems would be alleviated if instances of Span<T> are constrained to exist only on the execution stack.

Um problema adicional surge devido à composição.An additional problem arises due to composition. Geralmente, seria desejável criar tipos de dados mais complexos que seriam inseridos Span<T> e ReadOnlySpan<T> instâncias.It would be generally desirable to build more complex data types that would embed Span<T> and ReadOnlySpan<T> instances. Esses tipos compostos teriam de ser structos e compartilhariam todos os riscos e requisitos de Span<T> .Such composite types would have to be structs and would share all the hazards and requirements of Span<T>. Como resultado, as regras de segurança descritas aqui devem ser exibidas como aplicáveis a todo o intervalo de tipos de referência.As a result the safety rules described here should be viewed as applicable to the whole range of ref-like types.

A especificação de idioma de rascunho destina-se a garantir que os valores de um tipo de referência ocorram somente na pilha.The draft language specification is intended to ensure that values of a ref-like type occur only on the stack.

Tipos generalizados ref-like no código-fonteGeneralized ref-like types in source code

ref-like as structs são explicitamente marcadas no código-fonte usando o ref modificador: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;

A designação de uma struct como ref permitirá que a estrutura tenha campos de instância semelhantes a ref e também fará com que todos os requisitos de tipos de referência sejam aplicáveis à estrutura.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.

Representação de metadados ou estruturas semelhantes a refMetadata representation or ref-like structs

As estruturas semelhantes a ref serão marcadas com o atributo System. Runtime. CompilerServices. IsRefLikeAttribute .Ref-like structs will be marked with System.Runtime.CompilerServices.IsRefLikeAttribute attribute.

O atributo será adicionado às bibliotecas base comuns, como mscorlib .The attribute will be added to common base libraries such as mscorlib. Em um caso, se o atributo não estiver disponível, o compilador irá gerar um interno de forma semelhante a outros atributos incorporados sob demanda, como 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.

Uma medida adicional será tomada para evitar o uso de structs de referência em compiladores que não estejam familiarizados com as regras de segurança (isso inclui compiladores C# antes daquele em que esse recurso é implementado).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).

Não tendo outras boas alternativas que funcionem em compiladores antigos sem manutenção, um Obsolete atributo com uma cadeia de caracteres conhecida será adicionado a todas as estruturas semelhantes a ref.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. Os compiladores que sabem como usar tipos de referência irão ignorar essa forma específica de Obsolete .Compilers that know how to use ref-like types will ignore this particular form of Obsolete.

Uma representação de metadados típica:A typical metadata representation:

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

Observação: não é o objetivo de fazê-lo para que qualquer uso de tipos de referência em compiladores antigos falhe em 100%.NOTE: it is not the goal to make it so that any use of ref-like types on old compilers fails 100%. Isso é difícil de atingir e não é estritamente necessário.That is hard to achieve and is not strictly necessary. Por exemplo, sempre há uma maneira de contornar o Obsolete uso do código dinâmico ou, por exemplo, criar uma matriz de tipos de referência por meio de reflexão.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.

Em particular, se o usuário quiser colocar um Obsolete atributo ou Deprecated em um tipo de referência, não haverá outra opção além de não emitir o predefinido, uma vez que o Obsolete atributo não pode ser aplicado mais de uma vez.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..

Exemplos: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;
}


Especificação da linguagem de rascunhoDraft language specification

Abaixo, descrevemos um conjunto de regras de segurança para tipos de referência, ref struct a fim de garantir que os valores desses tipos ocorram apenas na pilha.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. Um conjunto de regras de segurança diferente e mais simples seria possível se os locais não puderem ser passados por referência.A different, simpler set of safety rules would be possible if locals cannot be passed by reference. Essa especificação também permitiria a reatribuição segura de locais de referência.This specification would also permit the safe reassignment of ref locals.

Visão geralOverview

Nós associamos a cada expressão em tempo de compilação o conceito de qual escopo a expressão tem permissão para escapar, "seguro para escapar".We associate with each expression at compile-time the concept of what scope that expression is permitted to escape to, "safe-to-escape". Da mesma forma, para cada lvalue, mantemos um conceito de qual escopo uma referência a ele tem permissão para escapar, "ref-safe-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". Para uma determinada expressão lvalue, elas podem ser diferentes.For a given lvalue expression, these may be different.

Eles são análogos ao "seguro para retornar" do recurso de locais de referência, mas é mais refinado.These are analogous to the "safe to return" of the ref locals feature, but it is more fine-grained. Onde o "seguro-para-retornar" de uma expressão registra apenas se (ou não) ele pode escapar do método delimitador como um todo, os registros seguros para o escape para os quais o escopo pode escapar (em qual escopo ele pode não escapar).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). O mecanismo de segurança básico é imposto da seguinte maneira.The basic safety mechanism is enforced as follows. Devido a uma atribuição de uma expressão E1 com um escopo de segurança para escape S1, para uma expressão (lvalue) E2 com escopo seguro para escape S2, será um erro se S2 for um escopo maior do que 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. Por construção, os dois escopos S1 e S2 estão em uma relação de aninhamento, porque uma expressão legal sempre é segura para retornar de algum escopo delimitando a expressão.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.

Por enquanto, é o suficiente para a finalidade da análise, dar suporte a apenas dois escopos – externos ao método e ao escopo de nível superior do método.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. Isso ocorre porque os valores like ref com escopos internos não podem ser criados e os locais de referência não dão suporte à reatribuição.That is because ref-like values with inner scopes cannot be created and ref locals do not support re-assignment. As regras, no entanto, podem dar suporte a mais de dois níveis de escopo.The rules, however, can support more than two scope levels.

As regras precisas para a computação do status de segurança para retorno de uma expressão e as regras que regem a legalidade de expressões, seguem.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-escaperef-safe-to-escape

O ref-safe-to-escape é um escopo, colocando uma expressão lvalue, à qual é seguro para uma ref ao lvalue para escapar.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. Se esse escopo for o método inteiro, dizemos que uma referência ao lvalue é segura para retornar do método.If that scope is the entire method, we say that a ref to the lvalue is safe to return from the method.

seguro para escaparsafe-to-escape

O seguro para escapar é um escopo, delimitando uma expressão, à qual é seguro para o valor escapar.The safe-to-escape is a scope, enclosing an expression, to which it is safe for the value to escape to. Se esse escopo for o método inteiro, dizemos que o valor é seguro para retornar do método.If that scope is the entire method, we say that the value is safe to return from the method.

Uma expressão cujo tipo não é um ref struct tipo é seguro para retornar de todo o método delimitador.An expression whose type is not a ref struct type is safe-to-return from the entire enclosing method. Caso contrário, nos referimos às regras abaixo.Otherwise we refer to the rules below.

ParâmetrosParameters

Um lvalue que designa um parâmetro formal é ref-safe-to-escape (por referência) da seguinte maneira:An lvalue designating a formal parameter is ref-safe-to-escape (by reference) as follows:

  • Se o parâmetro for um ref out parâmetro,, ou in , ele será de ref-safe-to-escape de todo o método (por exemplo, por uma return ref instrução); caso contrário,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
  • Se o parâmetro for o this parâmetro de um tipo struct, ele será de ref-safe-to-escape para o escopo de nível superior do método (mas não de todo o próprio método); Exemplo deIf 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
  • Caso contrário, o parâmetro é um parâmetro de valor e ele é de referência segura para escape para o escopo de nível superior do método (mas não do próprio método).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).

Uma expressão que é um Rvalue que designa o uso de um parâmetro formal é segura para escapar (por valor) de todo o método (por exemplo, por uma return instrução).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). Isso também se aplica ao this parâmetro.This applies to the this parameter as well.

LocaisLocals

Um lvalue que designa uma variável local é ref-safe-to-escape (por referência) da seguinte maneira:An lvalue designating a local variable is ref-safe-to-escape (by reference) as follows:

  • Se a variável for uma ref variável, seu ref-safe-to-escape será obtido do ref-safe-to-escape de sua expressão de inicialização; caso contrário,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
  • A variável é ref-safe-to-escape no escopo no qual ele foi declarado.The variable is ref-safe-to-escape the scope in which it was declared.

Uma expressão que é um Rvalue que designa o uso de uma variável local é segura para escapar (por valor) da seguinte maneira:An expression that is an rvalue designating the use of a local variable is safe-to-escape (by value) as follows:

  • Mas a regra geral acima, um local cujo tipo não é um ref struct tipo é seguro para retornar de todo o método delimitador.But the general rule above, a local whose type is not a ref struct type is safe-to-return from the entire enclosing method.
  • Se a variável for uma variável de iteração de um foreach loop, o escopo de seguro para escape da variável será o mesmo que o seguro para escapar da foreach expressão do loop.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.
  • Um local do ref struct tipo e não inicializado no ponto de declaração é seguro para retornar de todo o método delimitador.A local of ref struct type and uninitialized at the point of declaration is safe-to-return from the entire enclosing method.
  • Caso contrário, o tipo da variável é um ref struct tipo, e a declaração da variável requer um inicializador.Otherwise the variable's type is a ref struct type, and the variable's declaration requires an initializer. O escopo de segurança para escape da variável é o mesmo que o de segurança para escapar de seu inicializador.The variable's safe-to-escape scope is the same as the safe-to-escape of its initializer.

Referência de campoField reference

Um lvalue que designa uma referência a um campo, e.F , é ref-safe-to-escape (por referência) da seguinte maneira:An lvalue designating a reference to a field, e.F, is ref-safe-to-escape (by reference) as follows:

  • Se e for de um tipo de referência, ele será de ref-safe-to-escape de todo o método; caso contrário,If e is of a reference type, it is ref-safe-to-escape from the entire method; otherwise
  • Se e for de um tipo de valor, seu ref-safe-to-escape será obtido do ref-safe-to-escape de e .If e is of a value type, its ref-safe-to-escape is taken from the ref-safe-to-escape of e.

Um Rvalue que designa uma referência a um campo, e.F , tem um escopo seguro para escapar , que é o mesmo que o seguro para escapar do 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.

Operadores, incluindo ?:Operators including ?:

O aplicativo de um operador definido pelo usuário é tratado como uma invocação de método.The application of a user-defined operator is treated as a method invocation.

Para um operador que produz um Rvalue, como e1 + e2 ou c ? e1 : e2 , o seguro- para-escape do resultado é o escopo mais estreito entre os operandos do operador de segurança para 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. Em virtude disso, para um operador unário que produz um Rvalue, como +e , o seguro para escapar do resultado é o seguro para escapar do operando.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.

Para um operador que produz um lvalue, como c ? ref e1 : ref e2For an operator that yields an lvalue, such as c ? ref e1 : ref e2

  • a ref-safe-to-escape do resultado é o escopo mais estreito entre o ref-safe-to-escape dos operandos do operador.the ref-safe-to-escape of the result is the narrowest scope among the ref-safe-to-escape of the operands of the operator.
  • o seguro para escapar dos operandos deve concordar, e isso é o seguro para escapar do lvalue resultante.the safe-to-escape of the operands must agree, and that is the safe-to-escape of the resulting lvalue.

Invocação de métodoMethod invocation

Um lvalue resultante de uma invocação de método ref-retorne e1.M(e2, ...) é ref-safe-to-escape do menor dos seguintes escopos:An lvalue resulting from a ref-returning method invocation e1.M(e2, ...) is ref-safe-to-escape the smallest of the following scopes:

  • O método de circunscrição inteiroThe entire enclosing method
  • o ref-safe-to-escape de todas ref as out expressões de argumento e (excluindo o receptor)the ref-safe-to-escape of all ref and out argument expressions (excluding the receiver)
  • Para cada in parâmetro do método, se houver uma expressão correspondente que seja um lvalue, sua ref-safe-to-escape, caso contrário, o escopo do delimitador mais próximoFor 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
  • o seguro para escapar de todas as expressões de argumento (incluindo o receptor)the safe-to-escape of all argument expressions (including the receiver)

Observação: o último marcador é necessário para manipular código comoNote: the last bullet is necessary to handle code such as

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

ouor

return ref M(sp, 0);

Um Rvalue resultante de uma invocação de método e1.M(e2, ...) é seguro para escapar do menor dos seguintes escopos:An rvalue resulting from a method invocation e1.M(e2, ...) is safe-to-escape from the smallest of the following scopes:

  • O método de circunscrição inteiroThe entire enclosing method
  • o seguro para escapar de todas as expressões de argumento (incluindo o receptor)the safe-to-escape of all argument expressions (including the receiver)

Um RvalueAn Rvalue

Um Rvalue é ref-safe-to-escape do escopo de delimitação mais próximo.An rvalue is ref-safe-to-escape from the nearest enclosing scope. Isso ocorre, por exemplo, em uma invocação, como M(ref d.Length) Where d é do tipo dynamic .This occurs for example in an invocation such as M(ref d.Length) where d is of type dynamic. Ele também é consistente com (e talvez incorporou) nossa manipulação de argumentos correspondentes aos in parâmetros.It is also consistent with (and perhaps subsumes) our handling of arguments corresponding to in parameters.

Invocações de propriedadeProperty invocations

Uma invocação de propriedade ( get ou set ) tratada como uma invocação de método do método subjacente pelas regras acima.A property invocation (either get or set) it treated as a method invocation of the underlying method by the above rules.

stackalloc

Uma expressão stackalloc é um Rvalue que é seguro para escapar do escopo de nível superior do método (mas não de todo o próprio método).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).

Invocações de ConstrutorConstructor invocations

Uma new expressão que invoca um Construtor obedece às mesmas regras que uma invocação de método que é considerada para retornar o tipo que está sendo construído.A new expression that invokes a constructor obeys the same rules as a method invocation that is considered to return the type being constructed.

Além disso, o safe-to-escape não é mais largo do que o menor de todos os argumentos /operandos das expressões de inicializador de objeto, recursivamente, se o inicializador estiver presente.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.

Construtor de spanSpan constructor

O idioma depende Span<T> não ter um construtor da seguinte forma: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); 
}

Esse construtor faz com Span<T> que sejam usados como campos indistinguíveis de um ref campo.Such a constructor makes Span<T> which are used as fields indistinguishable from a ref field. As regras de segurança descritas neste documento dependem de os ref campos não serem uma construção válida em C# ou .net.The safety rules described in this document depend on ref fields not being a valid construct in C# or .NET.

Expressões defaultdefault expressions

Uma default expressão é segura para escapar de todo o método delimitador.A default expression is safe-to-escape from the entire enclosing method.

Restrições de idiomaLanguage Constraints

Gostaríamos de garantir que nenhuma ref variável local, e nenhuma variável do ref struct tipo, se refere à memória de pilha ou a variáveis que não estão mais ativas.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. Portanto, temos as seguintes restrições de idioma:We therefore have the following language constraints:

  • Nem um parâmetro ref, nem um local ref, nem um parâmetro ou local de um ref struct tipo podem ser levantados em uma função lambda ou local.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.

  • Nem um parâmetro ref nem um parâmetro de um ref struct tipo pode ser um argumento em um método Iterator ou um async método.Neither a ref parameter nor a parameter of a ref struct type may be an argument on an iterator method or an async method.

  • Nem um local de referência, nem um local de um ref struct tipo pode estar no escopo no ponto de uma yield return instrução ou uma await expressão.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.

  • Um ref struct tipo não pode ser usado como um argumento de tipo ou como um tipo de elemento em um tipo de tupla.A ref struct type may not be used as a type argument, or as an element type in a tuple type.

  • Um ref struct tipo não pode ser o tipo declarado de um campo, exceto que ele pode ser o tipo declarado de um campo de instância de outro 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.

  • Um ref struct tipo não pode ser o tipo de elemento de uma matriz.A ref struct type may not be the element type of an array.

  • Um valor de um ref struct tipo não pode ser Boxed:A value of a ref struct type may not be boxed:

    • Não há nenhuma conversão de um ref struct tipo para o tipo object ou o tipo System.ValueType .There is no conversion from a ref struct type to the type object or the type System.ValueType.
    • Um ref struct tipo não pode ser declarado para implementar qualquer interfaceA ref struct type may not be declared to implement any interface
    • Nenhum método de instância declarado em object ou em System.ValueType , mas não substituído em um ref struct tipo, pode ser chamado com um receptor desse ref struct tipo.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.
    • Nenhum método de instância de um ref struct tipo pode ser capturado pela conversão de método para um tipo delegado.No instance method of a ref struct type may be captured by method conversion to a delegate type.
  • Para uma reatribuição de referência ref e1 = ref e2 , o ref-safe-to-escape de e2 deve ser pelo menos tão amplo quanto um escopo como ref-safe-to-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.

  • Para uma instrução ref Return return ref e1 , o ref-safe-to-escape de e1 deve ser ref-safe-to-escape do método inteiro.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: também precisamos de uma regra que e1 deve ser segura para escapar do método inteiro ou que seja redundante?)(TODO: Do we also need a rule that e1 must be safe-to-escape from the entire method, or is that redundant?)

  • Para uma instrução return return e1 , a segurança para escapar do e1 deve ser segura para escapar do método inteiro.For a return statement return e1, the safe-to-escape of e1 must be safe-to-escape from the entire method.

  • Para uma atribuição e1 = e2 , se o tipo de e1 for um ref struct tipo, o safe-to-escape e2 deve ser pelo menos o maior de um escopo como o seguro para escapar de 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.

  • Para uma invocação de método se houver um ref out argumento ou de um ref struct tipo (incluindo o receptor), com o E1 de segurança para escape , nenhum argumento (incluindo o receptor) poderá ter um limite mais estreito do que o 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. AmostraSample

  • Uma função local ou anônima pode não se referir a um local ou parâmetro do ref struct tipo declarado em um escopo delimitador.A local function or anonymous function may not refer to a local or parameter of ref struct type declared in an enclosing scope.

Abrir problema: Precisamos de alguma regra que nos permita produzir um erro quando precisar despejar um valor de pilha de um ref struct tipo em uma expressão Await, por exemplo no códigoOpen 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);

SobreExplanations

Essas explicações e exemplos ajudam a explicar por que muitas das regras de segurança acima existemThese explanations and samples help explain why many of the safety rules above exist

Argumentos de método devem corresponderMethod Arguments Must Match

Ao invocar um método em que há um out ref parâmetro, que é um que ref struct inclui o receptor, todas as ref struct necessidades precisam ter o mesmo tempo de vida.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. Isso é necessário porque o C# deve tomar todas as suas decisões sobre a segurança do tempo de vida com base nas informações disponíveis na assinatura do método e no tempo de vida dos valores no site de chamada.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.

Quando há ref parâmetros que ref struct , em seguida, há o possibilidade que podem alternar em todo o seu conteúdo.When there are ref parameters that are ref struct then there is the possiblity they could swap around their contents. Portanto, no local de chamada, devemos garantir que todas essas trocas potenciais sejam compatíveis.Hence at the call site we must ensure all of these potential swaps are compatible. Se o idioma não tiver aplicado isso, ele permitirá um código inadequado como o seguinte.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; 
}

A restrição no receptor é necessária porque, embora nenhum dos seus conteúdos seja ref-safe-to-escape, ele pode armazenar os valores fornecidos.The restriction on the receiver is necessary because while none of its contents are ref-safe-to-escape it can store the provided values. Isso significa que, com tempos de vida incompatíveis, você poderia criar um buraco de segurança de tipo da seguinte maneira: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); 
}

Estruturar este escapeStruct This Escape

Quando se trata de estender as regras de segurança, o this valor em um membro de instância é modelado como um parâmetro para o membro.When it comes to span safety rules, the this value in an instance member is modeled as a parameter to the member. Agora, para um struct tipo de this é, ref S na verdade, onde é class simplesmente S (para membros de um class / struct nome de um).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).

Ainda this tem diferentes regras de escape que outros ref parâmetros.Yet this has different escaping rules than other ref parameters. Especificamente, não é válido para ref-safe-to-escape enquanto outros parâmetros são: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;
}

A razão para essa restrição realmente tem pouco a ver com a struct invocação de membro.The reason for this restriction actually has little to do with struct member invocation. Há algumas regras que precisam ser configuradas em relação à invocação de membro em struct Membros em que o receptor é um 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. Mas isso é muito acessível.But that is very approachable.

A razão para essa restrição é, na verdade, sobre a invocação de interface.The reason for this restriction is actually about interface invocation. Especificamente, isso se resume se o exemplo a seguir deve ou não ser compilado;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();
}

Considere o caso em que o T é instanciado como um struct .Consider the case where T is instantiated as a struct. Se o this parâmetro for ref-safe-to-escape, o retorno de p.Get poderá apontar para a pilha (especificamente, poderia ser um campo dentro do tipo instanciado de 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). Isso significa que o idioma não pôde permitir a compilação deste exemplo, pois ele poderia retornar um ref para um local de pilha.That means the language could not allow this sample to compile as it could be returning a ref to a stack location. Por outro lado, se this não for ref-safe-to-escape, p.Get não poderá se referir à pilha e, portanto, será seguro retornar.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.

É por isso que a saída de this em um struct é realmente tudo sobre as interfaces.This is why the escapability of this in a struct is really all about interfaces. Ele pode ser absolutamente feito para funcionar, mas tem uma compensação.It can absolutely be made to work but it has a trade off. O design eventualmente surgiu em favor de tornar as interfaces mais flexíveis.The design eventually came down in favor of making interfaces more flexible.

No entanto, é possível relaxar isso no futuro.There is potential for us to relax this in the future though.

Considerações futurasFuture Considerations

Comprimento um Span <T> sobre valores de referênciaLength one Span<T> over ref values

Embora não seja legal hoje, há casos em que a criação de um comprimento uma Span<T> instância em um valor seria benéfica: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);
}

Esse recurso será mais atraente se compararmos as restrições em buffers de tamanho fixo , pois isso permitiria Span<T> instâncias de comprimento ainda maior.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.

Se houver alguma necessidade de reduzir esse caminho, o idioma poderá acomodar isso, garantindo que essas Span<T> instâncias estivessem apenas para frente.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. Isso é que, no momento , eles eram seguros para o escopo no qual foram criados.That is they were only ever safe-to-escape to the scope in which they were created. Isso garante que o idioma nunca tenha que considerar um ref valor que escape um método por meio ref struct de um retorno ou campo de 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. Isso provavelmente também exigiria outras alterações para reconhecer esses construtores como capturar um ref parâmetro dessa maneira.This would likely also require further changes to recognize such constructors as capturing a ref parameter in this way though.