Sub vs. Function without a return value

The real question to be asking is not why Sub exists, but why Function exists! Why?

VBA is built on top of VB6 which is built entirely on top of COM. unfortunately I can't find the source for this but, all COM methods must return a HRESULT. This means that all VBA/VB6 methods are, once compiled, sub-routines!

But if all methods are sub routines, how come my method returns a value? Well, let's look at an example ISynchronizeHandle::GetHandle:

HRESULT GetHandle(
  HANDLE *ph
);

As you can see, the parameter for the return value is actually supplied by reference in the DLL C++ header definition. By convention this return type is always the last parameter. So for IStdMarshalInfo::GetClassForHandler the definition is:

HRESULT GetClassForHandler(
  DWORD dwDestContext,
  void  *pvDestContext,
  CLSID *pClsid
);

where the class returned is returned as the CLSID of the class (last parameter).

The C++ header for Application.Evaluate() would look something like this:

HRESULT Evaluate(
     char[] sToEvaluate,
     char[] *sEvaluated
);

This would be similar to us implementing a sub-routine as follows:

Sub Evaluate(ByVal sToEvaluate as string, ByRef sEvaluated as string)

End Sub

And thus whenever we call the function we'd need to prepare a return type first and then call the sub.

Dim sRet as string
Evaluate("1+1",sRet)

This kind of sucks... So Microsoft figured "Hey, let's give the VB engine a way to return data. We'll just wrap the existing sub behaviour under the hood, but our VM will handle the real result and return that to the user's function". So instead of extending the existing sub-behaviour, they likely just wrapped the behaviour and created a new declare Function.

Ultimately Function was only implemented as an afterthought to Sub. This is why Sub exists at the onset.


Bear in mind that you can make a custom VOID class for example and then write:

Function someFunction() as VOID

End Function

and then call your function like:

Call someFunction()

But it isn't advised.


It is possible (not advisable, nor a good practice) to use boolean functions instead of Subs everywhere and make sure that they even return True once they reach the end.

Like this:

Public Function Main as Boolean
     'All the code here
     Main = True
End Function

This is easy to test with one line:

Debug.Print Main

Then you can use it like this:

If Not SomeFunction Then IncrementLogString ("SomeFunction") And at the end, you may check the log with all the functions, that were false.


To be honest, I have only done this once, about 5 years ago, because the other dev insisted on it and I did not have a choice. Concerning the fact, that it was probably the biggest VBA application I have seen and it ran smoothly (I was not the main developer there, thus taking no credit there), I guess there is no problem in it. After some time I got used to it and it was fun. But in general, people would frown upon it.


I can think of a couple reasons off the top of my head.

  • It prevents the caller from trying to assign the non-existent return value to something:

    Sub example()
        Dim x
        x = Foo     '<-- Potential runtime error.
        x = Bar     '<-- Compile error, Expected Function or variable.
    End Sub
    
    Function Foo()
    End Function
    
    Sub Bar()
    End Sub
    
  • In Excel, it allows it to be used as a macro (assuming that it doesn't have arguments).

  • It's less efficient, because it has to push the return value onto the stack.
  • It's unclear to somebody else who is reading the code what the intention is.

    Function Foo()
        'Do some stuff
        'WTH is the return value not assigned?!
    End Function
    
  • It (should, assuming otherwise decent coding practices) signals that it should not have side effects. A Sub is expected to have side-effects.


Specifically regarding the edit.

I can use Function without declaring a return value and have the same, no?

This is not a correct statement. If you declare a function like this...

Function Foo()
    'Do some stuff
End Function

...it still has a return value - it is just implicit. The code above is exactly equivalent to:

Public Function Foo() As Variant
    'Do some stuff
End Function

VBA doesn't force you to explicitly declare the type of the return value (similar to how it doesn't require an explicit type for a Dim statement). That does not mean that it doesn't have one - it does.


Because it clarifies the intent.

A Function clearly says "I'll have something for you when I return". The expectation is that a Function returns something, because that's what functions are meant to do.

A Sub clearly says "I'm doing something that you should expect to just eventually succeed". The expectation is that a Sub executes an action, alters some state, causes some side effects.

A Function that would be named DoSomething, is just as confusing as a Sub that would be named GetFoo: the intent is obscured, the very nature of the procedure conflicts with how it's advertised. I expect DoSomething to either succeed at doing something, or throw some error. Similarly, I expect GetFoo to, well, get me a Foo.


Because a non-returning function makes no sense.

In several programming languages, a Function (or semantically similar construct) that doesn't return a value in all code paths, can't even be compiled. Using a Function-without-a-return-value for everything in VBA sounds very much like abusing the language, just because VBA won't complain about it. As common wisdom tells us, it's not because we can, that we should.

Why return void, when you can return a bool everywhere, and not assign it?

public bool DoSomething()
{
    // do stuff...
    // ...and don't assign the return value.
    // woopsie, doesn't compile.
}

A VBA Sub procedure is like a C# void method: it's explicit about its non-returning nature, and that's a good thing.


Because static code analysis tools will complain.

The VBA compiler will notoriously not care if you write code where it's never clear whether your non-returning of an implicit return value is intentional or not.

When you do mean to return a value - and forget, because bugs happen all the time - how can you be sure that this one is legitimately non-returning, and that other one isn't? Without combing through the code and fully understanding everything it does and why, you can't tell. If you're lucky, you're looking at small, specialized functions that obviously do one thing, and do it well. Otherwise, you need to waste your time understanding what's going on, just to be sure of something that should already be obvious.

Static code analysis tools like Rubberduck (I maintain that project) will flag these functions, since they are potential bugs hiding in your code base, waiting to bite you:

Rubberduck code inspection results

Tags:

Vba