Assigned vs <> nil

TL;DR

The official documentation states

Assigned(P) corresponds to the test P <> nil for a pointer variable, and @P <> nil for a procedural variable.

Hence, for a non-procedural pointer variable (such as a variable of type PInteger, PMyRec, TBitmap, TList<integer>, or TFormClass), Assigned(P) is the same thing as P <> nil.

However, for a procedural variable, Assigned(P) is the same thing as @P <> nil, while P <> nil would try to execute the procedure or function that P points to (with an empty list of parameters).

Explanation

The documentation excerpt above summarizes it pretty well. Assigned(X) returns True if and only if the X variable, which has to be a pointer under the hood (with some exception), has a non-nil value.

Assigned can be used for "old-school" pointer variables:

var
  i: Integer;
  p: PInteger;
begin
  i := 5;

  p := @i;
  { Assigned(p)        True }
  { p <> nil           True }

  p := nil;
  { Assigned(p)        False }
  { p <> nil           False }

Assigned can also be used for object (and metaclass) variables. Indeed, in Delphi, an object (or metaclass) variable is simply a pointer under the hood:

L := TList<integer>.Create;
try
  { Assigned(L)        True }
  { L <> nil           True }
finally
  FreeAndNil(L);
end;

{ Assigned(L)          False }
{ L <> nil             False }

(And, for completeness, an example with a metaclass variable:

var
  FC: TFormClass;
begin

  FC := TForm;
  { Assigned(FC)       True }
  { FC <> nil          True }

  FC := nil;
  { Assigned(FC)       False }
  { FC <> nil          False }

)

In all these examples, Assigned(X) is exactly the same thing as X <> nil.

However, for procedural types, things are slightly different.

First, let's warm up:

type
  TStringProc = procedure(const AText: string);

procedure MyStrProc(const AText: string);
begin
  ShowMessage(AText);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  SP: TStringProc;
begin
  SP := MyStrProc;
  SP('test');
end;

Notice in particular that SP is used to actually invoke the procedure it currently points to.

Now, you might try

procedure TForm1.FormCreate(Sender: TObject);
var
  SP: TStringProc;
begin
  SP := MyStrProc;
  ShowMessage(BoolToStr(Assigned(SP), True)); { True }
  ShowMessage(BoolToStr(SP <> nil, True)); { will not compile }

  SP := nil;
  ShowMessage(BoolToStr(Assigned(SP), True)); { False }
  ShowMessage(BoolToStr(SP <> nil, True)); { will not compile }
end;

but that will not even compile. The compiler says, "Not enough actual parameters". The reason is that the above code will try to execute the procedure that SP points to, and then indeed the required AText parameter is missing. (Of course, at compile time, the compiler doesn't know if SP will point to a compatible procedure or not, but it does know the signature of such a valid procedure.)

And even if the procedural type had an empty parameter list, it wouldn't compile, since a procedure doesn't return a value (much less a value that can be compared against nil).

But beware! The following code will compile:

type
  TGetPtrFunc = function: pointer;

function MyPtrFunc: pointer;
begin
  Result := nil;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  PF: TGetPtrFunc;
begin
  PF := MyPtrFunc;
  ShowMessage(BoolToStr(Assigned(PF), True)); { True }
  ShowMessage(BoolToStr(PF <> nil, True)); { False (!) }

  PF := nil;
  ShowMessage(BoolToStr(Assigned(PF), True)); { False }
  ShowMessage(BoolToStr(PF <> nil, True)); { will cause access violation at runtime }
end;

The first PF <> nil will compare the MyPtrFunc function result value against nil; it will not tell you whether the PF function pointer is assigned or not (it is!).

The second PF <> nil will try to invoke a nil function pointer; that's a bug (access violation exception).

To test if a procedural variable is assigned, you have to test @PF <> nil:

procedure TForm1.FormCreate(Sender: TObject);
var
  SP: TStringProc;
begin
  SP := MyStrProc;
  ShowMessage(BoolToStr(Assigned(SP), True)); { True }
  ShowMessage(BoolToStr(@SP <> nil, True)); { True }

  SP := nil;
  ShowMessage(BoolToStr(Assigned(SP), True)); { False }
  ShowMessage(BoolToStr(@SP <> nil, True)); { False }
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  PF: TGetPtrFunc;
begin
  PF := MyPtrFunc;
  ShowMessage(BoolToStr(Assigned(PF), True)); { True }
  ShowMessage(BoolToStr(@PF <> nil, True)); { True }

  PF := nil;
  ShowMessage(BoolToStr(Assigned(PF), True)); { False }
  ShowMessage(BoolToStr(@PF <> nil, True)); { False }
end;

For procedural variables, Assigned(X) is the same thing as @X <> nil, as stated by the documentation.

Methods

Methods work as regular procedures as far as this topic is concerned. For instance, for a method variable M, Assigned(M) is equivalent to @M <> nil and is True iff the method pointer is not nil. (Under the hood, I believe @M yields the Code member of TMethod.)

procedure TForm1.FormCreate(Sender: TObject);
var
  M: TNotifyEvent;
begin
  M := Self.FormClick;
  ShowMessage(BoolToStr(Assigned(M), True)); { True }
  ShowMessage(BoolToStr(@M <> nil, True)); { True }

  M := nil;
  ShowMessage(BoolToStr(Assigned(M), True)); { False }
  ShowMessage(BoolToStr(@M <> nil, True)); { False }
end;

What to use?

So, should you use Assigned(X) or X <> nil for non-procedural pointers? And should you use Assigned(X) or @X <> nil for procedural pointers? It is entirely a matter of taste.

Personally, I tend to use Assigned(X) when I want to test if the variable is assigned, and X = nil (or @X = nil) when I want to test if the variable is not assigned, simply because not Assigned(X) is less compact.

A related warning

Of course, both Assigned(X) and X <> nil (or @X <> nil) only test if the pointer is nil or not; if non-nil, the pointer might still point to garbage. For instance, since local non-managed variables are not initialized in Delphi, they might well be non-nil before they are assigned a value, but in that case they point to garbage:

procedure TForm1.FormCreate(Sender: TObject);
var
  L: TList<integer>; { local non-managed variable: not initialized }
begin
  Assigned(L) { True or False (chance). If True, it points to garbage data. }
              { Bad things will happen if you try to use L as a list here }
              { (especially if L is not nil). }

Another example:

  L := TList<integer>.Create;
  try
    { Do things with L }
  finally
    L.Free;
  end;

  Assigned(L); { True, but L points to garbage -- don't use it as a list! }