11

What is the sense of a subroutine (Sub)? Why not use a Function without a return value?


Edit

What I mean is, why does the keyword Sub exist? I can use Function without declaring a return value and have the same, no?

Comintern
  • 21,855
  • 5
  • 33
  • 80
testo
  • 1,052
  • 2
  • 8
  • 24
  • 4
    Why use a `Function` to return a single value when you can use multiple `ByRef` parameters and return 20 values with a `Sub`? – Mathieu Guindon Oct 11 '18 at 16:59
  • But I can use ByRef in a Function too, not? – testo Oct 11 '18 at 17:07
  • 4
    @doev I think the comment was meant facetiously; you _could_ use `ByRef` to return values but do you _want_ to? Do you _want_ to inherit a codebase that does that, which makes it very hard to understand and violate the [Principle of Least Astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment)? – this Oct 11 '18 at 17:14
  • ah, I see. Maybe I have to clearify my question. I will edit. – testo Oct 11 '18 at 17:43

4 Answers4

28

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

Mathieu Guindon
  • 69,817
  • 8
  • 107
  • 235
  • 1
    Realistcally, it could be argued that `Function something() as void` also clarifies intent and it could be argued it does so more readily also. – Sancarn Jan 13 '19 at 16:43
  • @Sancarn except there's no `void` in VBA, and languages that have it don't need to tell `Sub` from `Function`... not sure what you're getting at. – Mathieu Guindon Jan 13 '19 at 16:45
  • By this I mean, according to this logic, there really is no reason it was implemented as 2 seperate keywods, rather than 1 keyword and the existence of `void`. Anyway I remember reading, that the reason the `sub` keyword actually exists, is because COM methods must always return a value. And as such a void method literally cannot be implemented in COM. Thus `sub` wraps the underlying `Function` method causing it to not return a value. I think I read that in [Hardcore Visual Basic](http://vb.mvps.org/hardweb/mckinney.htm) though I'm unsure... – Sancarn Jan 13 '19 at 16:52
  • 1
    @Sancan you are correct, there's really no reason to have both, but only language designers can answer the "why" of that, so I focused on why `Sub` and `Function` should both be used when writing VBA code, since they both exist. Using non-returning `Function` procedures just because one doesn't like or understand why there's a `Sub` keyword in the language, is hurting one's code for the reasons explained in this answer. Now maybe the OP was after something else, given they never accepted any answer they got, IDK. – Mathieu Guindon Jan 13 '19 at 17:05
  • Seems like MS is trying to have it both ways, a distinction without a difference, except when there is a difference. What I mean is you can define the return value as ANYTHING; Single, Double, BSTR, Interger, Long, etcetera - except for VOID. I found out the hard way that when calling a (void) DLL using the "Function", they still allocate a 4 byte word on top of the stack, didn't see that documented anywhere. Other programming languages allow you to declare your function (void), think LabView and Python. – Danny Holstein Mar 26 '20 at 20:16
10

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.

Comintern
  • 21,855
  • 5
  • 33
  • 80
  • 2
    Just a FYI - in Access' case, a function can be only used in macros & expression, even if you have nothing to return because AIUI, Access need to be able to keep track of whether a function was called and it can't do that with sub. The upshot is that in Access VBA projects there may be some functions-faking-as-a-sub in order to satisfy this macro requirement. In those situations, though, it's usually fine to just return a `Boolean` value. – this Oct 11 '18 at 17:59
  • 3
    @this Along those same lines, I wouldn't really consider it poor practice to return a "success" `Boolean` for a side-effecting `Function`. – Comintern Oct 11 '18 at 18:03
4

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.

FreeMan
  • 5,660
  • 1
  • 27
  • 53
Vityata
  • 42,633
  • 8
  • 55
  • 100
  • 2
    If you're forcing the caller to `If`-check every returning function, you'll inevitably end up with `Else : 'shouldn't happen`. Things like file I/O should be *expected* to succeed, and **throw** if it doesn't, because that would be *exceptional*. Forcing control-flow checks everywhere *just because*, is likely hiding bugs and happily resuming in failure paths. – Mathieu Guindon Oct 11 '18 at 19:00
  • @MathieuGuindon - yup, indeed, we were using `If Main() Then` quite a lot as far as I remember. From what I know almost up to now, it is still running quite ok. – Vityata Oct 11 '18 at 19:07
  • 2
    While I agree that there is a use case for this (see my comment under my answer), I'd argue that it should be the exception rather than the rule. In most cases I would go as far as to say that raising an error would be more appropriate. (Not my DV either, BTW). – Comintern Oct 11 '18 at 19:12
4

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.

Sancarn
  • 2,575
  • 20
  • 45