defer — limpeza junto de quem abre
O comando adiado até o fim do bloco: abrir e fechar lado a lado, em ordem inversa, por qualquer saída.
O que é e por que existe#
Todo recurso que se abre precisa ser fechado: arquivo, conexão, trava, objeto temporário. O try…finally resolve, mas separa as duas pontas: o Free fica dezenas de linhas abaixo do Create, e cada novo recurso aninha mais um try. O defer coloca o fechamento logo depois da abertura, e o compilador garante que ele roda quando o bloco termina.
LFile := TResource.Create('arquivo');
defer LFile.Free; // roda no fim do bloco, aconteça o que acontecer
LSocket := TResource.Create('conexão');
defer LSocket.Free; // registrado depois, roda ANTESÉ açúcar sobre o try…finally que já existia: A; defer B; C; equivale a A; try C; finally B; end;. Por isso valem as regras do finally.
Sintaxe e regras#
defer <comando>; // um comando simples
defer begin ... end; // ou um bloco- Quando roda: ao terminar o bloco que contém o
defer, por qualquer saída: fim normal,Exit,Break/Continueque saem do bloco, ou exceção. - Ordem: vários
deferrodam em ordem inversa à da escrita (LIFO), o que espelha "abrir A, abrir B, fechar B, fechar A". - Argumentos avaliados no fim: como num
finally, e não na hora dodefer(como em Go). Para congelar um valor, copie-o para uma variável local antes. Resultjá está gravado: comExit(V), o valor é decidido antes de odeferrodar; umdefer LValue := 99não muda o que foi devolvido.- Em laço: o
deferdo corpo roda ao fim de cada volta, inclusive quando a volta termina emContinueouBreak. - Variáveis gerenciadas: um objeto local (ARC) é solto depois dos
deferdo bloco, mesmo que tenha sido declarado depois dodefer.
program Defer;
// Demo: `defer` adia um comando até a saída do bloco atual.
// Várias saídas possíveis (fim normal, Exit, Continue, Break, exceção) -> a limpeza roda sempre.
// Os defers rodam em ordem inversa (LIFO): o último registrado roda primeiro.
// Isso casa com "abrir A, abrir B ... fechar B, fechar A".
procedure Process(AFail: Bool8);
begin
WriteLn('abre recurso A');
defer WriteLn('fecha recurso A'); // registrado primeiro, roda por último
WriteLn('abre recurso B');
defer WriteLn('fecha recurso B'); // registrado depois, roda primeiro
if AFail then
raise 'falhou no meio'; // mesmo com exceção, os dois defers rodam
WriteLn('trabalho feito');
end procedure;
// O defer roda DEPOIS de Result ser gravado: o valor devolvido já foi decidido.
function Value: Int32;
var
LValue: Int32;
begin
LValue := 1;
defer LValue := 99; // não altera o que Exit devolve
Exit(LValue);
end function;
var
I: Int32;
begin
WriteLn('--- sem falha');
Process(False);
WriteLn('--- com falha');
try
Process(True);
except
on E: Exception do WriteLn('capturou: ', E.Message);
end try;
WriteLn('--- Result já gravado: ', Value);
WriteLn('--- defer em laço (roda a cada volta, inclusive no Continue)');
for I := 1 to 3 do
begin
defer WriteLn(' fim da volta ', I);
if I = 2 then
Continue;
WriteLn(' volta ', I);
end for;
end.--- sem falha
abre recurso A
abre recurso B
trabalho feito
fecha recurso B
fecha recurso A
--- com falha
abre recurso A
abre recurso B
fecha recurso B
fecha recurso A
capturou: falhou no meio
--- Result já gravado: 1
--- defer em laço (roda a cada volta, inclusive no Continue)
volta 1
fim da volta 1
fim da volta 2
volta 3
fim da volta 3Demo 40. Os dois defer rodam em ordem inversa, também com exceção; Value devolve 1 mesmo com o defer; no laço o defer roda a cada volta.
O demo 36 mostra a mesma ideia com objetos de verdade, e a ordem em relação ao ARC (primeiro os defer, depois os objetos locais são soltos):
program Defer;
// Demo 36: defer - "faça isto quando o bloco terminar", junto de onde abriu.
//
// Em vez de separar "abrir" e "fechar" por dezenas de linhas (ou usar um
// try/finally aninhado para cada recurso), escreva o fechamento logo depois
// da abertura. Os defer rodam em ordem INVERSA (o último registrado roda
// primeiro) e rodam mesmo se uma exceção ou Exit interromper o bloco.
type
TResource = class
private
FName: UString;
public
constructor Create(const AName: UString);
destructor Destroy; override;
end class;
constructor TResource.Create(const AName: UString);
begin
FName := AName;
WriteLn(' abriu ', FName);
end constructor;
destructor TResource.Destroy;
begin
WriteLn(' fechou ', FName);
inherited;
end destructor;
procedure Work(AFail: Bool8);
var
LFile, LSocket: TResource;
begin
WriteLn('início (falhar = ', AFail, ')');
LFile := TResource.Create('arquivo');
defer WriteLn(' defer 1: registrado primeiro, roda por último');
LSocket := TResource.Create('conexão');
defer WriteLn(' defer 2: registrado depois, roda primeiro');
if AFail then
raise 'algo deu errado no meio';
WriteLn(' corpo terminou normalmente');
// ao sair: primeiro rodam os defer (2, depois 1); só então as variáveis
// locais são soltas e os objetos (ARC) fecham.
end procedure;
var
I: Int32;
begin
Work(False);
try
Work(True);
except
on E: Exception do WriteLn('capturou: ', E.Message);
end try;
// em laços, o defer roda ao fim de CADA volta (também com Continue/Break)
for I := 1 to 3 do
begin
defer WriteLn(' fim da volta ', I);
if I = 2 then
Continue;
WriteLn('volta ', I);
end for;
end.início (falhar = FALSE)
abriu arquivo
abriu conexão
corpo terminou normalmente
defer 2: registrado depois, roda primeiro
defer 1: registrado primeiro, roda por último
fechou arquivo
fechou conexão
início (falhar = TRUE)
abriu arquivo
abriu conexão
defer 2: registrado depois, roda primeiro
defer 1: registrado primeiro, roda por último
fechou arquivo
fechou conexão
capturou: algo deu errado no meio
volta 1
fim da volta 1
fim da volta 2
volta 3
fim da volta 3Demo 36. Os defer fecham na ordem inversa; só depois a contagem de referência solta os objetos.
Armadilhas e erros típicos#
- Corpo direto de
if/while/for:if X then defer ...;não tem bloco para adiar até o fim dele. O erro éFE0651: odefersó vale diretamente dentro de umbegin…end; escreva o corpo comobegin … end. Break,ContinueeExitdentro do comando adiado são recusados, pela mesma razão que não se sai de umfinally.deferé palavra contextual: só vale no começo de um comando seguido de outro comando.defer := 1,defer(x)edefer.Foocontinuam sendo identificadores comuns. Consequência:defer;sem comando não tem erro próprio; sai "identificador não declarado".- Só em minúsculas, como as demais palavras da linguagem.
Veja também#
- Nulos e exceções, para o
try…finallypor baixo. - Memória, para a ordem em que o ARC solta os objetos.
- Demos
40-defere36-defer; manual do compilador, seção "defer".