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: const, var 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 (var, out 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-chave | Pode ler? | Valor inicial é mantido? | Pode escrever? | Uso comum |
|---|---|---|---|---|
const | Sim | Sim | Não | Apenas leitura |
var | Sim | Sim | Sim | Leitura/Escrita |
out | Sim* | Não (descartado) | Sim | Retorno 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 thevar,out, orconstrefdirective), 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, é:
- O Delphi ignora o valor anterior no sentido semântico: você não deveria confiar nele, mas ele ainda está na memória.
- O compilador não apaga o conteúdo da variável ao início da função.
- O compilador exige que um
outseja inicializado antes de ser lido, mas não impede que você o leia — apenas que você não pode confiar que seja válido. - Se você ler o
outlogo no começo sem inicializá-lo, você pode ter acesso ao valor anterior que estava na memória, como ocorreu comLResult = 10.
⚠️ Isso significa que out é perigoso?
Não exatamente, mas reforça que:
- A diretiva
outé uma convenção de uso, não uma imposição de comportamento imediato (como:= nilou:= 0). - O 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:
Emboraoutindique 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 (Integer,Boolean, 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ído | Tipo inferido |
|---|---|
123 | Integer |
'Texto' | String |
True ou False | Boolean |
3.14 | Double |
Now | TDateTime |
TStringList.Create | TStringList |
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:
- 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.
- 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.
- 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.
- Evita poluição da seção
var: em programas longos, o blocovarvira um "cemitério de nomes". Com inline, isso é reduzido.
✨ Destaque importante:
A 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
varquando o valor anterior é irrelevante - Esquecer de inicializar parâmetro
out - Passar
constcom tipo mutável (ex:TStringList) e alterá-lo dentro - Achar que
outcria uma nova variável — ele aponta para a original - Usar
Assignedemoutpara 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.
constdiz: "Prometo não alterar."vardiz: "Vou usar e talvez modificar."outdiz: "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 out, var, const 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.