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 Sub
s 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: