TheCodeNaked

out, var, const e variáveis inline: quando o compilador entende sua intenção melhor que você (Copy)


Introdução

Por que usamos var para tudo?

Quantas vezes você escreveu var Resultado: Integer em Delphi apenas por costume, mesmo quando não precisava ler nem modificar o valor anterior? Se você não lembra da última vez que usou out, você não está sozinho.

O Delphi oferece três palavras-chave (ou diretivas) para declarar parâmetros em métodos e funções: constvar e out. E cada uma delas tem um significado semântico claro. Usar a errada não quebra o código, mas compromete a clareza, a intenção e até a performance.

⚠️ Se você não declarar nenhuma diretiva (varout ou const), o parâmetro será passado por valor, ou seja, o Delphi irá alocar uma cópia na memória. Isso pode significar uso duplicado de memória — especialmente grave se você estiver lidando com estruturas grandes, listas, strings ou streams. Pior: essa cópia pode ser silenciosa e ineficiente.


Tabela Comparativa

Palavra-chavePode ler?Valor inicial é mantido?Pode escrever?Uso comum
constSimSimNãoApenas leitura
varSimSimSimLeitura/Escrita
outSim*Não (descartado)SimRetorno de valor

out permite leitura após ser inicializado dentro do método chamado. O valor anterior é ignorado, mas dentro do método você pode ler e manipular normalmente.


Quando usar cada um deles

const

Para passar dados imutáveis por referência, principalmente tipos grandes:

procedure MostraNome(const Nome: string);
begin
  ShowMessage(Nome);
end;

Isso evita cópias desnecessárias e garante que o valor não será alterado.


var

Quando você precisa ler e escrever no valor:

procedure AumentaEmDobro(var Valor: Integer);
begin
  Valor := Valor * 2;
end;

out

Quando a variável é apenas um resultado da função, e o valor antigo não importa:

procedure CriarImagemVazia(out Bmp: TBitmap);
begin
  Bmp := TBitmap.Create;
  Bmp.SetSize(200, 200);
  Bmp.Clear(TAlphaColors.White);
end;

procedure DesenharLogotipo;
var
  Logo: TBitmap;
begin
  CriarImagemVazia(Logo);
  try
    // faz o desenho
  finally
    Logo.Free;
  end;
end;

O compilador garante que Bmp seja sobrescrito antes do uso. Quem chama o método é responsável por declarar a variável localmente e passá-la como referência para que o método atribua seu valor. A diferença é que o valor anterior será ignorado automaticamente.

Um ponto pouco explorado: Logo e Bmp são a mesma variável. São apenas nomes diferentes para o mesmo endereço na memória, compartilhado entre quem chama e quem executa. Muitos desenvolvedores não percebem isso — e por isso é comum ver códigos onde o nome do parâmetro e da variável local são mantidos iguais, para deixar essa relação mais clara.

Na prática, out é um ponteiro sem cara de ponteiro. O Delphi mascara bem esse detalhe, mas não muda a verdade técnica: a variável está sendo passada por referência.


Mas então... out e var são ponteiros?

Sim. Confirmado pela própria documentação oficial da Embarcadero:

"When a parameter is passed by reference (using the varout, or constref directive), the address of the variable is passed rather than a copy of its value."

— Embarcadero DocWiki - Parameter Passing

Ou seja: o Delphi passa o endereço da variável, mesmo que você não veja ^@new ou dispose.

Isso quer dizer que, sim: se você já usou out, você já usou ponteiro. Talvez sem perceber.

E isso é bom e ruim:

  • Bom: porque simplifica para quem quer evitar ponteiros explícitos.
  • Ruim: porque esconde o mecanismo, o que dificulta entender ownership, performance, concorrência e vazamentos de memória.
“Você nunca viu um ponteiro? Talvez o Delphi só tenha escondido ele de você.”

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
  FMX.Controls.Presentation, FMX.StdCtrls;

type
  TForm1 = class(TForm)
    btnSoma_1: TButton;
    btnSoma_2: TButton;
    Button1: TButton;
    procedure btnSoma_1Click(Sender: TObject);
    procedure btnSoma_2Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    procedure Soma(var AValue_1, AValue_2: Integer; out AResult: Integer);
    procedure Soma_2(var AValue_1, AValue_2: Integer; out AResult: Integer);
    procedure AddOne(var X, Y: Integer);

    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.btnSoma_1Click(Sender: TObject);
var
  LValue_1 : Integer;
  LValue_2 : Integer;
  LResult : Integer;
begin
  LValue_1 := 1;
  LValue_2 := 2;
  LResult := 10;

  Soma(LValue_1, LValue_2, LResult);
end;

procedure TForm1.btnSoma_2Click(Sender: TObject);
begin
//  Soma_2(1,2,3); // [dcc32 Error] Unit1.pas(47): E2033 Types of actual and formal var parameters must be identical
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
begin
  I := 1;
  AddOne(I, I);

  ShowMessage(IntToStr(I));

end;

procedure TForm1.Soma(var AValue_1, AValue_2: Integer; out AResult: Integer);
begin
  AResult := AResult;
  ShowMessage('O Valor do AResult [out] é: ' + IntTostr(AResult));  // Resultado é 10 - Não descartou o valor inicial.

  AResult := AValue_1 + AValue_2;
  ShowMessage('O Valor do AResult [out] é: ' + IntTostr(AResult)); // Resultadi é 3 - Está certo, foi sobreescrito.
end;


procedure TForm1.Soma_2(var AValue_1, AValue_2: Integer; out AResult: Integer);
begin
  AResult := AValue_1 + AValue_2;
end;



procedure TForm1.AddOne(var X, Y: Integer);
begin
  X := X + 1;
  Y := Y + 1;
end;


end.

Realização de Prova Conceitual

Vamos realizar um teste para verificar se a diretiva out realmente descarta o valor anterior da variável passada, conforme consta na documentação da Embarcadero e em muitos materiais de referência — e quando exatamente isso ocorre.

📌 Ponto de destaque

Você fez:

LResult := 10;
Soma(LValue_1, LValue_2, LResult);


E dentro do método:

AResult := AResult; // Apenas leitura
ShowMessage(...); // Exibe "10" (valor anterior ainda existe!)

O valor não foi zerado automaticamente ao entrar no método, contrariando a crença popular de que out faz isso automaticamente no momento da chamada.

🧠 Conclusão técnica real:

O valor não é "zerado" magicamente pelo Delphi ao entrar no método. O que ocorre, de fato, é:

  1. O Delphi ignora o valor anterior no sentido semântico: você não deveria confiar nele, mas ele ainda está na memória.
  2. O compilador não apaga o conteúdo da variável ao início da função.
  3. compilador exige que um out seja inicializado antes de ser lido, mas não impede que você o leia — apenas que você não pode confiar que seja válido.
  4. Se você ler o out logo no começo sem inicializá-lo, você pode ter acesso ao valor anterior que estava na memória, como ocorreu com LResult = 10.

⚠️ Isso significa que out é perigoso?

Não exatamente, mas reforça que:

  • A diretiva out é uma convenção de usonão uma imposição de comportamento imediato (como := nil ou := 0).
  • compilador espera que você sobrescreva o valor antes de usá-lo, mas não zera automaticamente.
  • Esse comportamento pode causar leitura de lixo de memória se não houver inicialização explícita dentro do método.
  • Isso pode confundir quem acredita que o Delphi limpa automaticamente o out.

✅ O que pode estar confundindo a comunidade

A documentação muitas vezes diz que o valor "anterior será ignorado", mas isso se refere à intenção de uso, e não a uma limpeza real de memória.

🔍 Adicional importante

Se você alterar seu método assim:

procedure TForm1.Soma(var AValue_1: integer; var AValue_2: Integer; out AResult: Integer);
begin
ShowMessage('AResult antes da atribuição: ' + IntToStr(AResult)); // Aqui você pode obter o valor anterior ou lixo
AResult := AValue_1 + AValue_2;
end;

E deixar de inicializar LResult com 10, você pode exibir valores aleatórios (lixo), o que prova que a variável não é confiável até que seja sobrescrita.

🔥 Frase para nosso artigo

out não apaga o valor anterior. Apenas te obriga a sobrescrevê-lo. O que havia antes ainda está ali — visível, mas inválido.

💡 Proposta de melhoria para o artigo:

Podemos inserir no post:

Uma armadilha pouco documentada:

Embora out indique que o valor anterior será descartado, o Delphi não limpa a variável automaticamente. Ela continua com o conteúdo anterior — até que você sobrescreva.

O compilador espera que você inicialize a variável, mas não impede leitura antecipada (mesmo que errada). Isso pode causar lixo de memória ou confusão, especialmente com tipos simples (IntegerBoolean, etc.).

📊 Inline variable declaration

Desde o Delphi 10.3 é possível declarar variáveis com escopo reduzido e inferência automática de tipo:

begin
  var Numero := 42;                     // Integer
  var Texto := 'Bem-vindo';            // String
  var Ativo := True;                   // Boolean
  var Pi := 3.14159;                   // Double
  var Agora := Now;                    // TDateTime
  var Lista := TStringList.Create;     // TStringList
  try
    Lista.Add('Item 1');
  finally
    Lista.Free;
  end;
end;

Os tipos inferidos automaticamente são:

Valor atribuídoTipo inferido
123Integer
'Texto'String
True ou FalseBoolean
3.14Double
NowTDateTime
TStringList.CreateTStringList

Isso melhora a legibilidade e previne uso indevido fora do bloco.

Mas o que torna essa funcionalidade especialmente relevante é seu impacto direto na segurança, clareza e escopo de variáveis:

  1. Evita uso de variáveis globais: é difícil rastrear onde está sendo usada, quem a modificou, ou seu estado atual. Inline te obriga a pensar local.
  2. Facilita simulações e testes locais: quando a ideia aparece ali mesmo, você cria e testa rápido. É fabuloso para prototipagem, mas perigoso se mal utilizado.
  3. Escopo de vida é garantidamente curto: como a variável só existe dentro do bloco, é destruída automaticamente ao sair dele. Isso melhora previsibilidade e liberação de recursos.
  4. Evita poluição da seção var: em programas longos, o bloco var vira um "cemitério de nomes". Com inline, isso é reduzido.
✨ Destaque importante:

NASA, por meio das recomendações do Jet Propulsion Laboratory (JPL) em sua famosa lista Power of 10, recomenda explicitamente:

Declare all data objects at the smallest possible level of scope.

Ou seja: nada de variáveis globais. Escopo local sempre que possível.

Essa regra visa reduzir riscos, melhorar rastreabilidade e evitar efeitos colaterais invisíveis.

Fonte: NASA / Power of 10 - via Perforce
💬 E apesar de o Delphi ser uma linguagem fortemente tipada, essa funcionalidade de inferência com varmostra que há sim uma flexibilização moderna no uso de tipos — algo que aproxima o Delphi de práticas vistas em linguagens mais recentes, sem abrir mão da segurança do tipo estático.

⚠️ Armadilhas comuns

  • Usar var quando o valor anterior é irrelevante
  • Esquecer de inicializar parâmetro out
  • Passar const com tipo mutável (ex: TStringList) e alterá-lo dentro
  • Achar que out cria uma nova variável — ele aponta para a original
  • Usar Assigned em out para verificar valor antigo — isso não funciona
  • Abusar de variáveis inline sem organização pode resultar em código caótico, sem estrutura

🧬 Filosofia de intenção

Cada uma dessas palavras não só diz ao compilador como tratar a memória, mas também documenta a inteligência da função.

  • const diz: "Prometo não alterar."
  • var diz: "Vou usar e talvez modificar."
  • out diz: "Vou entregar algo novo."

Se você escreve var quando queria dizer out, está falando com o compilador de forma confusa. E o compilador pode até entender, mas o próximo humano que ler seu código talvez não.


📍 Conclusão

O Delphi é uma linguagem rica em expressividade. Saber usar outvarconst e inline não é frescura: é sinal de clareza mental e respeito por quem vai ler (e manter) seu código.

Escreva com intuição. Mas escreva também com intenção.


No próximo artigo, podemos ir mais fundo: ponteiros, referências, interfaces, ownership e o que realmente é destruído (ou não) quando você dá um Free.

Sobre o autor

TheCodeNaked

No TheCodeNaked, programar é consequência, não ponto de partida. Antes do código, vem a dúvida, a análise, o contexto. Não seguimos fórmulas — questionamos. Criar software é pensar com clareza. O resto é só digitação.

TheCodeNaked

Criar com clareza. Codificar com intenção.

TheCodeNaked

Ótimo! Você se inscreveu com sucesso.

Bem-vindo de volta! Você acessou com sucesso.

Você se inscreveu com sucesso o TheCodeNaked.

Sucesso! Verifique seu e-mail para acessar com o link mágico.

As suas informações de faturamento foram atualizadas.

Seu pagamento não foi atualizado