5 Motivos para Usar Variáveis Inline no Delphi

Publicado originalmente em: https://landgraf.dev/pt/5-motivos-para-usar-variaveis-inline-no-delphi/

O que é esse novo recurso de declaração de variáveis inline (inline variables) do Delphi, surgido na versão Rio 10.3?

Em resumo, é a possibilidade de se declarar uma variável em qualquer linha do seu código. Ou seja, você pode declarar uma variável da forma a seguir, dentro do bloco begin..end:

procedure Test;
begin
  var I: Integer;
  I := 22;
  ShowMessage (I.ToString);
end;

Muita gente já entendeu como esse recurso funciona, mas não entendeu por que ele é interessante. Neste artigo, vou mostrar essa nova funcionalidade com enfoque nas vantagens que ela traz.

1. Organiza seu código

A variável só passa a ser acessível a partir do ponto que ela é declarada. Pra muitas pessoas isso organiza melhor o código num método grande, pois é possível saber melhor onde aquela variável está sendo usada. Considere o seguinte código:

procedure Test;
var 
  A, B, C, D, E: Integer;
  Found, Done, Excluded: Boolean;
  Text: string;
begin
   // muitas
   // linhas
   // de 
   // código
end;

Pode ser confuso saber onde são usadas todas essas variáveis, quando elas estão sendo inicializadas, se ela já foi altera anteriormente, etc.. No código a seguir, sabemos que a variável Text por exemplo não existe no começo do código, e que ela só é utilizada ao final. Ninguém “mexeu” nela antes dessa parte do código:

procedure Test;
begin
   var A, C: Integer;
   // Não podemos usar o Text aqui!
   // linhas
   // de 
   // código

var Text: string;
// Text só pode ser usada aqui
end;

2. Minimiza bugs

Quem nunca cometeu esse deslize:

procedure Test;
var I: Integer;
begin
  for I := 0 to Count - 1 do
    Process;
  FazAlgoComI(I);
end;

Ou seja, usar a variável do for depois de terminado o loop. Isso não é seguro, e apesar do compilador emitir um warning pra isso, muitas pessoas o ignoram. Usando declaração inline do for, a variável só é válida dentro do for, e usá-la fora do bloco resultará em um erro de compilação:

procedure Test;
begin
  for var I: Integer := 0 to Count - 1 do
    Process;
  FazAlgoComI(I); // Erro de compilação!!
end;

A vantagem acima vem do fato de que o escopo das variáveis é limitado ao bloco em que elas são declaradas. Declarando as variáveis apenas dentro do escopo em que elas são usadas também minimiza a chance de erros. Por exemplo, suponha que você tem um código assim:

procedure Test;
var I: Integer;
begin
  I := CalculaAlgo;
  Persiste(I);
  // muuuuitas linha depois...
  Loga(I);
end;

Então você precisa refatorar esse código de modo que a primeira parte só aconteça em uma determinada condição. Você acha que só ali a variável I está sendo usada, e faz algo assim:

procedure Test;
var I: Integer;
begin
  if Condicao then
  begin
    I := CalculaAlgo;
    Persiste(I);
  end;
  // muuuuitas linha depois...
  Loga(I);
end;

Pronto, você esqueceu da última linha e talvez o valor de I não seja o que você espera. Alterando o escopo da sua variável para o bloco irá gerar erros de compilação caso a variável seja usada fora do bloco, o que vai lhe mostrar o problema imediatamente para você tomar uma decisão:

procedure Test;
begin
  if Condicao then
  begin
    var I: Integer;
    I := CalculaAlgo;
    Persiste(I);
  end;
  // muuuuitas linha depois...
  Loga(I); // Erro de compilação!
end;

3. Economiza digitação

Quem não quer mais produtividade? Se você puder digitar um pouco menos pra declarar uma variável, por que não? Agora é possível declarar e inicializar uma variável ao mesmo tempo:

procedure Test;
begin
  var I: Integer := 22; 
  ShowMessage (I.ToString);
end;

Mas não só isso. Há também a inferência de tipo, o que significa que na maioria dos casos você não precisa incluir o tipo da variável ao declará-la. Basta o código de inicialização para o Delphi saber qual é o tipo da variável.

Parece pouco? Imagine um caso onde o tipo da variável é uma coisa assombrosa usando generics:

procedure NewTest;
var
  MyDictionary: TObjectDictionary<string, TObjectList<TMyAmazingClass>>;
  Pair: TPair<string, TObjectList<TMyAmazingClass>>;
  List: TObjectList<TMyAmazingClass>;
begin
  MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
  MyDictionary.Add('one', CreateList);
  Pair := MyDictionary.ExtractPair('one');
  List := Pair.Value;
  ShowMessage(List.Count.ToString);
end;

Com a declaração inline e a inferência de tipo, você pode reescrever o código desta forma:

procedure NewTest;
begin
  var MyDictionary := TObjectDictionary<string, TObjectList<TMyAmazingClass>>.Create;
  MyDictionary.Add('one', CreateList);
  var Pair := MyDictionary.ExtractPair('one');
  var List := Pair.Value;
  ShowMessage(List.Count.ToString);
end;

Melhor, não?

4. Aumenta a performance

O fato de as variáveis pertencerem a um escopo mais limitado (dentro de um bloco begin..end) pode até aumentar a performance do código!

Você pode ver mais detalhes sobre isso neste excelente artigo: Inline Variables can increase performance. Mas em resumo: a variável será inicializada apenas se e quando a execução entrar no código, e será finalizada imediatamente quando a execução sair do código. Ou seja, neste código:

procedure TestInlineVars(const ACondition: Boolean);
begin
  // BEFORE
  if (ACondition) then
  begin
    var S := 'Inline String';
    var I: IInterface := TInterfacedObject.Create;
    var F: TFoo;
    F.S := 'Managed Record';
  end;
  // AFTER
end;

As variáveis S, I e F são tipos gerenciados (string, interface e record). O compilador adiciona automaticamente código para inicializar e finalizar essas variáveis.

Se você chamar a procedure TestInlineVars milhões de vezes, isso terá um impacto grande. Porém com o código acima, essas variáveis só serão inicializadas caso ACondition seja verdadeiro e o bloco seja efetivamente executado. Menos código desnecessário sendo executado.

5. Facilita o uso de diretivas de compilação

Até em pequenas coisas esse recurso pode ajudar. Isso me chamou a atenção ao ler este post: Unexpected Benefit of Inline Variables: Conditional Blocks, que mencionou esse benefício.

Se você usa diretivas de compilação onde declara diferentes variáveis para cada situação, você tem que colocar diretivas na declaração também:

procedure DoesSomething;
var
  {$IFDEF CASE1}
  var1: Integer;
  {$ENDIF}
  {$IFDEF CASE2}
  var2: Integer;
  {$ENDIF
begin
  {$IFDEF CASE1}
  // use var1
  {$ENDIF}
  {$IFDEF CASE2}
  // use var2
  {$ENDIF}
end;

Chato, hein? Acho que assim fica mais fácil e legível:

procedure DoesSomething;
begin
  {$IFDEF CASE1}
  var1: Integer;
  // use var1
  {$ENDIF}
  {$IFDEF CASE2}
  var2: Integer;
  // use var2
  {$ENDIF}
end;

Acredito que as variáveis inline tenham ainda outros sutis benefícios, em situações específicas, que não tenha listado aqui. Caso suspeite de mais algum, deixe seu comentário. Se não concordou com os benefícios e acha que as variáveis inline não são uma boa novidade para o Delphi, comente também. Só não se esqueça de uma coisa: se não gostou, é só não usar!