Obscure and obsolete VB syntax
Visual Basic 6.0 and Visual Basic for Applications support a great deal of questionable and not-so-useful legacy statements and keywords. This article looks into confusing VB statements and old-style syntax that is rare today.
A language with a long history will eventually have some outdated syntax features. Understanding old code may be hard if it contains undocumented keywords or commands you don't normally use. This is also true for Visual Basic.
Much of VB's old syntax comes from its DOS predecessors. The immediate predecessor of VB was Microsoft QuickBasic, a programming language for DOS. Syntax-wise, QuickBasic was quite similar to VB. For backwards compatibility, VB supports many syntax features that were useful in QuickBasic, even though they are no longer very useful in VB. What is more, the Visual Basic language has been updated between its various versions, rendering a part of the language obsolete.
This article focuses on the oddities of Visual Basic 6.0 and VBA. Reading the article helps you to avoid and eliminate potentially confusing code.
Call is a superfluous keyword to call sub-programs. The keyword doesnít do anything useful. You can remove it. Confusingly, if you do remove it, you will need to also remove the parentheses around the argument list. Otherwise you are likely to end up with an error. Therefore, it's often safer to leave
Call alone unless you want to introduce new bugs to your program.
A particularly nasty case is
Call with one parameter:
Call MySub(x). If you remove the word
Call, the code stays the same, right? Wrong. The parentheses in the
MySub (x) force passing x by value. If
MySub happens to declare the parameter as
ByRef and change its value on purpose, the change will no longer be propagated back to the caller. This could break existing code. So, if you do remove
Call, remember to remove the parentheses too. PA
Date = expr : Time = expr
Date$ = expr : Time$ = expr
You could set the current date and time with
Date = date and
Time = time. You could, if you were an Administrator. If not, you will receive a run-time error 70: Permission denied.
Why would have a VB program change the system clock? If the PC didnít have a battery-powered clock, it might be a good idea to let the user set the date and time. Todayís PCs get the time from an online time server. There is no need to ask the user what date it is.
Interestingly, these needless statements come in two versions: with and without the dollar sign. What's the point?
Declare Sub|Function name CDecl Lib "Ö" (Ö)
CDecl is an undocumented keyword in Visual Basic. According to online sources,
CDecl is only implemented in the Mac version of VBA. In all other versions itís a no-op. So what is it for?
In QuickBasic, the
Declare statement was used to call both Basic and non-Basic procedures. The default was Basic, which meant that procedure arguments were passed from left to right. According to QuickBasic 4.5 help, the optional
CDecl keyword specified that the declared procedure used C-language argument order instead: arguments were passed from right to left. This means that
CDecl = pass arguments from right to left.
In Visual Basic (less the Mac VBA version), all
Declare statements pass their arguments from right to left. Thus,
CDecl doesn't do anything any more.
CDecl can be omitted and will be deleted by VB IDE. Confusingly enough,
CDecl is not the same as
cdecl. In Windows, there are 2 different calling conventions to consider:
stdcall. Event though they are different, both of them use right-to-left passing order. Visual Basic supports only
cdecl. Even if you manage to add
CDecl to your
Declare statement, you can't have VB use
DefBool | DefByte | DefInt | DefLng | DefLngLng | DefLngPtr | DefCur | DefSng | DefDbl | DefDate | DefStr | DefObj | DefVar letter1[-letter2]
DefType statements declare a data type for variables that would otherwise become Variants. They affect declared variables without a data type specification and also undeclared local variables.
DefType was handy in the QuickBasic era. The default datatype was
Single, and there was no way to require the declaration of variables. You would use type declaration suffixes such as
DefType you could get rid of the suffixes without changing your habits of not declaring your variables.
DefInt I would set all i-variables to
DefStr S would force s-variables to
String and so forth. This way you could have the variable tell its data type by its initial letter.
In Visual Basic, the default datatype is
Variant, which is much more flexible if you happen to leave your variables undeclared. Variants are so flexible your code will usually work as intended even if you know nothing about data types. What is more, you can use
Option Explicit to require explicit declaration of all local variables.
DefType has thus become less useful. It may be confusing because developers may not expect to find it in todayís code. Bugs are likely to get introduced when the data type of an untyped variable is not
Variant as one would normally expect. PA
Dim Shared varname
Shared is a keyword previously used with
Dim statements. It has no effect.
Shared exists for backwards compatibility with pre-VB languages such as Microsoft QuickBasic and Basic Compiler. In QuickBasic,
Dim Shared was used to declare module-level variables to be available in procedures, similar to module-level variables in VB.
Shared has no effect in VB because all module-level variables are always available in the procedures of the module. If you write
Dim Shared in VB6, the word
Shared disappears and only
x Eqv y
Eqv operator makes a bitwise equivalence comparison of two integers.
Eqv is not an ďis equivalent toĒ operator but rather a kind of ď
Not XorĒ operator.
x Eqv y is the same as
Not (x Xor y).
Itís important to realize that
Eqv is not an alternative to the equals operator (
2 = 3 returns
2 Eqv 3 returns a non-zero result and is
Eqv will return zero only when no bits of the operands are equal.
Erl function is used in error handlers. It returns the line number where an error occurred. If there is no line number on the line that failed,
Erl returns the preceding line number.
Erl doesnít work with line labels. It doesnít work with large line numbers either, the current maximum being 65535. If the line number is any larger,
Erl will return an unexpected number instead. A major disadvantage of
Erl is that it requires the source to be line numbered, which is rarely the case with VB, unless you use a tool such as VB Watch that deliberately adds the line numbers for error handling.
Floating point numbers with D exponent
Floating point numbers, which are usually written like
1E+6, can also be written in an alternative syntax:
1D+6. Both represent a million: 6 zeros after 1.
The legacy D-syntax originally declared a
Double value, while the E-syntax was for
Single values. According to VB 1.0 help, the largest E-value was
3.402823E38, but with D you could go up to
In VB6 and VBA, there is no difference any more. They will automatically replace D with E. You can still have D-values in strings, though. Thus,
Val("1D+6") = 1000000. This can be confusing if you donít know the explanation.
Global is a synonym for
Public. In the good old days,
Global was the only VB keyword that could modify the scope of a variable or a constant. Keywords
Friend didnít exist.
VB6 and VBA still allow you to use
Global to define variables and constants. But, if you try it on a
Type, VB will (normally) delete the word and behave as if you had written
line: Ö : Return
GoSub is a way to call a sub-routine within the procedure itself.
GoSub will jump to the given line, execute the statements following it up to a
Return statement. A
Return will resume from the statement next to
This was the only way to have sub-routines before Subs and Functions were available. That is, a long time before VB was invented. You wouldnít normally use
GoSub in VB. Subs and Functions are much more structured.
There is still one special use for
GoSub in VB. Thatís in a procedure with a lot of local variables. If you want to access local variables in another
Function, you need to either pass them as parameters or move variable definitions to the module level. This may not always be desirable. By implementing the sub-routine with
Return, you avoid passing the local data, which may come handy. This can easily result in an unnecessarily complex routine, though. So it's best to leave
GoSub unused if you donít really, really need it. PA
If condition GoTo line
Did you know you can write an
If statement without
Then? Yes, itís true, but for single-line
If statements only.
If..GoTo is the equivalent of
If..Then GoTo. VB6 and VBA will add the
Then keyword automatically, but technically
If..GoTo is still acceptable.
If..Then 10 Else 20
If condition Then linenum [Else linenum]
Did you know the
GoTo statement is optional after
Else? This is true in single-line
If statements. You can omit
GoTo and simply add the target line number. This wonít work with line labels, though. This was originally a documented feature of the VB language. VB6 and VBA, however, will add the omitted
GoTo, forcing you to use
GoTo and not the shorter
x Imp y
Imp is the logical-implication operator: x → y. Few people understand it correctly. The rationale behind
Imp is hard to grasp unless youíre involved with logic. Itís best to rewrite it.
x Imp y is the same as
(Not x) Or y. The latter one is much more clearóunless all developers working on the code remember logic operators from their logic course.
Int function returns the integer part of a decimal number? Wrong. It doesnít. It returns the largest integer less than or equal to the number.
Int(3.1) = 3, but
Int(-3.1) = -4. This is a source of errors and confusion.
Fix function will always return the integer part.
Fix(-3.1) = -3. When negative numbers are expected,
Fix is more natural than
[Let] variable = value
Let keyword is superfluous. There is no reason to use it. Let doesnít add anything to your program, nor does it make it easier to read or understand. You can always omit it. VB does allow you to write it, but it should automatically delete it, really. PA
Line numbers are truly outdated Basic. A long, long time ago, all Basic lines were numbered. This is no longer the case. Line numbers tend to appear only as targets of
On Error GoTo and
Resume. Instead of line numbers, you can use the much more descriptive line labels. Line numbers are rare now.
The only useful use for line numbers is error handling. In an error handler, the Erl function returns the line number where the error occurred (as long as the number is 65535 or less). This functionality is not available in any other way.
In VB6 and VBA, the minimum line number is 0 and the undocumented maximum is 999999999 (9 digits). Line numbers donít have to appear in numeric order. Itís perfectly valid to start with 1000 and go down to 0.
LSet variable = value
RSet variable = value
RSet are designed to left-align and right-align text in a string variable.
LSet x = y will copy the contents of
x while maintaining the current length of
x is too short, the string is truncated. If
x is longer than
y, the rest is padded with spaces on the right, making the string left-aligned.
RSet does the same, but the padding will appear on the left instead, making the string right-aligned.
These statements are apparently designed for fixed-length strings. Back in the good old DOS days, fixed-length text fields were common on displays and reports, which used fixed-pitch fonts. As user interfaces have become more flexible, fixed-length strings are not so common any longer.
LSet can also be used to copy a variable of a user-defined type onto other user-defined types. This could be dangerous and should only be used with care.
Next with multiple variables
Next variable, variable, variableÖ
You donít necessarily need a
Next statement for each
For statement. A single
Next statement can end multiple nested
Next k, j, i is the equivalent of
Next k : Next j : Next i. This syntax makes sense rarely if ever. PA
Octal numbers are expressed in digits 0-7. For example,
&O10 is the octal notation for decimal 8.
Octal numbers can be considered obsolete. Since an octal digit represents 3 bits, they were useful on systems with a word size divisible by 3: 6-bit words, 12-bit words and so on. Todayís systems donít use such word sizes. Visual Basic runs on computers with 8-bit bytes and 32-bit or 64-bit words. In this environment, hexadecimal and decimal numbers are handier and much more common. Since octal numbers are relatively rare, they can cause problems reading the code. To avoid confusion, express numeric values in either decimal or hexadecimal format, which are more widely understood. PA
On Error GoTo -1
The largely undocumented
On Error GoTo -1 statement is obscure. This statement doesnít set an error handler, nor does it unset it like
On Error GoTo 0 does. The statement clears the error that just occurred in preparation for a new error to occur. It can be used in an error handler that could trigger a new error. If an error occurs within an error handler, execution of the procedure will stop (and flow back to caller or cause a crash). Calling
On Error GoTo -1 allows you to set a ďnested error handlerĒ for taking care of errors occurring in an error handler. Example:
On Error GoTo mainhandler
MsgBox "Error occurred"
On Error GoTo -1 ' Clear the error
On Error GoTo nestedhandler
MsgBox "Error occurred in error handler"
In this example,
Error 5 will trigger
Error 6 will trigger
nestedhandler. Should you leave out the line with
On Error GoTo -1,
nestedhandler would not execute.
On number GoSub line1, line2, line3Ö
On number GoTo line1, line2, line3Ö
On..GoTo are primitive alternatives to
Select Case. Both statements provide a way to execute one of multiple branches based on number. If number is one, execution continues on line1, if itís two, then line2 and so on.
GoTo statements alone are not structured programming. The same can be said about
On..GoTo, which are about as obscure as VB statements can get. The existence of these statements in VB code calls for a rewrite. PA
On Local Error
On [Local] Error GoTo|Resume Ö
Local keyword in an
On Error statement has no effect. In Visual Basic,
On Error statements are local whether you use the
Local keyword or not. Leave
Local out to avoid confusing anyone with the difference of local and non-local error handlers, as the only option is a local error handler anyway. PA
Local is a legacy keyword. According to VB 1.0 help, it was originally intended for compatibility with Microsoft QuickBasic and Basic Compiler. The documentation appears incorrect because QuickBasic 4.5 doesnít support it.
Operator + on strings
string1 + string2
The plus (
+) operator will usually concatenate two strings. For any two strings,
a + b = a & b. But, if either argument is a number, VB will attempt to sum them up:
"2" + "3" = "23"
"2" + 3 = 5
Confusing! An error occurs when adding text to a number:
"a" + 3 ⇒ error
Itís best to avoid
+ with strings. VB should warn about it.
Option Basic 0|1
Option Base is a legacy statement that defines the lower bound of arrays. The possible lower bounds are 0 and 1, the default being 0.
The statement is unnecessary in Visual Basic. VB has always let programmers declare the lower bound of arrays in
Option Private Module
This statement is completely useless in VB6. It does have a use in VBA, though. In VBA,
Option Private Module prevents the contents of a module from being used outside its own project.
Rem is an old way to add remarks to a program. Visual Basic has supported the handier apostrophe (
') syntax since version 1.0. There has never been any reason to use
Rem. Itís there for backwards compatibility only. PA
Reset statement closes all open files and supposedly writes all file buffers to disk. The
Close statement, if not followed by any file numbers, also closes all files, but it doesnít flush buffers. According to VB 1.0 help,
Reset was supposed to be called after
Close. The documentation of VB 6.0 doesnít refer to this use. Itís unclear if there is any actual difference. Another issue is, why on earth would you want to keep your files open and close them all at once? Wouldnít you want to close each file as soon as itís not required, not leaving any used files open unnecessarily? Itís better to use
Close filenum to explicitly close each file right after use and not attract bugs by leaving files open and closing them with a single statement.
In an error handler,
Resume 0 will restart execution from the statement that failed.
Resume 0 is exactly the same as
Resume without the zero. The zero is both unnecessary and confusing. Anyone reading the code could easily think execution will redo from line 0 and wonder where line 0 might be. Itís best not to have the zero in the statement.
Static attribute in a procedure header forces all local variables to be
Static in that procedure. This is a way to avoid declaring each variable with the
Static varname statement. This programming style possibly dates back to the era when local variables were not declared at all.
Static variables are preserved between calls to the procedure. Thatís all right. Having all variables
Static is another thing. It could confuse a person whoís reading or modifying the code. There are uses for
Static variables, but itís more explicit to declare each variable with the
Static statement rather than relying on the
Static keyword in the procedure declaration. Thus, stay away from
Static Function and
Static Property, and use
Static varname instead.
Type declaration characters (
Type declaration characters are from an era before Visual Basic. VB has always allowed you to declare datatype with the
As Datatype clause. You can declare !
As Single, #
As Double, $
As String, %
As Integer, &
As Long, @
As Currency and ^
As LongLong. The last option is available in VBA 7. PA
While condition : statements : Wend
While..Wend loop syntax is very old.
Do..Loop, available already before VB was invented, has replaced it a long time ago. Today you should use
Do..Loop as itís more versatile. For most uses,
While..Wend is exactly the same as
While..Wend could confuse developers who donít recognize the obsolete syntax. PA
A special case requiring the use of
While..Wend does exist. This is when you need to nest two or more loops and you want to exit several loops with one command.
If quit Then Exit Do
In this example,
Exit Do will exit both the
While loops simultaneously. Clever! You couldnít do this with two nested
Width #filenum, value
Width statement is intended for writing text files. It forces a fixed line length on an output file. The
Width statement affects successive
Print # statements but not
Write # statements. If a line would become longer than desired, text will wrap to the next line in the output file. While this could be useful in some cases, forced line wrapping in the middle of words isnít a very modern thing.