Const Qualifiers and You
Note: As with all things regarding scope, Const qualifiers may be a bit difficult to understand. You should have a thorough understanding of variable scope before attempting to understand Const qualfiers.
Also note my cliche title, which I chose because of it's clicheness.
What the heck are Const qualifiers? Const qualifiers are a feature recently added to the language (fbc 0.18.3); they're a standard part of C++ and now they exist in FreeBasic too. Const qualifiers are yet another form of protection - they allow some "variables" to act like constants to certain parts of your program, in other words some parts of the program are allowed to access (read) them but not modify them. Just another kind of type safety, really, but an extremely useful one. In particular, they are very useful in OO situations, but you can probably benefit from them to some degree even if you aren't interested in OOP.
The Const qualifier in FreeBasic is essentially an extension to data type declarations, and they may be used with Dim, UDT members, and procedure parameters. Generally you put it right after the "As" part of the variables's data type declaration:
(By the way, throughout this tutorial I use only Integers and Integer Ptrs as examples - however, Const qualifiers should work the same way with all other variable types, including Types, Enums, and anything else that declares something. If for any reason it doesn't, it's probably a bug and you should report it.)
Note in this case we are allowed to change it once - when we create it. But after that, you may not change it any more. In fact, you must initialize it - the compiler will give an error if you don't (interestingly, you are allowed to set it equal to "Any", in which the contents are not guaranteed and could be anything). But you may not do anything that modifies it after that. It will actually give you an error if, for example, you try to do something like this:
Yet, since this doesn't change the variable any, you can do
Now this is all very good, but it doesn't seem much different from the normal usage of Const. That is, the following two lines so far seem to mean, for all practical purposes, the same thing:
Do they? Not quite. You see, the Const qualifier allows you to create consts that act as variables except that they can't be modified. That means you can put them inside Types and other places. What's more, you can put them inside Sub/Function declarations - and this is a very key reason for their existence:
Normally functions are allowed to modify the variables you send to them. Of course, whether they modify the original variable or just a local copy of the variable depends on whether you use ByVal or ByRef (and of course pointers is a whole different things altogether), but they normally are allowed to modify a variable. This may be undesirable, for whatever reason, and the Const qualifier exists to prevent that. In the function given above, some_num can be modified by the function. Normally it would only be a local copy that is modified, which is fine, since it won't affect the original Const Integer, but what if we declare the function like this?
Now my_sub has direct access to whatever variable you pass to it, and for that reason you are not allowed to do this sort of thing
Why? Simply because the function may modify the variable. We don't know for sure that it will, of course, but it might, so we can't do that. In fact, the error you'll get if you try to compile that is "Invalid assignment/conversion." It's almost as if the Const Integer is a different variable type, but only when it's ByRef. In that case, it would act like trying to pass a string to an integer argument (or vice-versa). Yet if it's not passed ByRef, we don't have a problem, since there's no way the function can possibly modify the variable!
And of course, if we did something like this:
Then it compiles just fine, but if you try to do the following within the function, you get an error:
Why? Once again, the original variable has been passed ByRef to the sub. It's now in local scope, but because it's ByRef, any modifications to the variable would modify the original, which cannot be done. Once again, it's entirely possible to create a copy of the variable and modify it all you want:
But you can't modify some_num itself!
Now we come to pointers. What about them? For pointers it's a bit more complicated; it's possible to declare the pointer itself as Const, OR what the pointer points to - or even BOTH! So all of the following are valid:
The first one makes it so you can change the pointer itself all you want, but not the data that the pointer points to (even if you change *what* the pointer points to). The second allows you to change what the pointer points to, but you can't make it point to anything else. The third won't let you change what the pointer points to OR the pointer itself! In all cases you can make a copy of the pointer - but it must be a Const Integer Ptr or a Const Integer Const Ptr since otherwise you would be able to change the contents of whatever the original pointer points to! This is great protection against anything being modified!
In case the behaviour of the Const qualifier seems a bit strange to you, I'll explain exactly how it decides what's safe to allow and what isn't. It can actually be summed up pretty quickly: The Const qualifier aims to protect the original data. It doesn't care if you make a copy of the data, or change that copy, it just doesn't want you to be able to change the original data. Remembering this will help you a great deal. Of course, it needs to know what the original data is, which is why when there's pointers involved there are so many different places to put the Const qualifier (and you can even put it in twice - or more, depending on how many pointers there are!) So long as you remember what the Const qualifier is for, you'll never have any difficulty figuring out where to put it - or even if you need it at all (or if you need to not use it).
You can also use the Const qualifier in UDTs. In fact, it's actually a very important thing to OOP (in a similar fashion to Namespaces, which while not a direct part of OOP nevertheless are very much related) - but even if you don't use OOP you can still use Const qualifiers in your Types. I don't even really need to show you an example, as it's pretty obvious by now how it works, but here's an example for you:
And obviously this won't compile, since the member t_int is Const. Furthermore, you can also declare the variable of that type (in this case, t) with the Const qualifier. The following will not compile either, since ALL members of t are Const:
As for the OOP side of things (and if you aren't interested in OOP you can skip this part) - you may be wondering about methods. Methods implicitly pass the object ByRef as this when called. Is there a way to create constant objects? Of course! We've already seen that. But some object methods will modify the object, and some won't. Is there a distinction? The answer is yes. As of November 23, 2007, we now have Const procs. That means you can do this:
Once again, the way this works is based on the simple rule. Since the implicitly passed copy of this is passed ByRef, any method is normally able to modify the contents of the object - and if the object is declared As Const, that's not supposed to happen! Thus, there are essentially two kinds of method. The two kinds are given names in the C++ documentation page (listed below in the references): there are mutators and inspectors. Mutators may modify objects, but inspectors do not. Thus, for objects declared As Const, only the inspector methods for those objects may be used - while all methods may be called for non-Const objects. The inspector methods are, of course, the ones declared as Const methods. Thus, for Const objects only their Const methods may be used.
This is all very good, but some of you may be asking - Why do I need this? Well, a direct answer would be out of the *scope* (heh) of this tutorial, so I'll counter - why do we need scope at all? The reason for Const qualifiers (and the future Const methods) is the same as the reason for scope within procedures and modules, and the same reason for hiding of variables in objects: because we want to be certain that something won't unexpectedly change in the middle of the program, when we least expect it. Sometimes we want things to change, and that's when we don't use the Const qualifier. But when you want something to stay what it is, you use the Const qualifier, and you can be certain it will not change (and the compiler won't compile the code if there is danger of it happening!) This is the definition of Const, how it works, and it's the reason you use it! And in general, it's the reason you use any scoping control or data hiding.
Some final notes
If you use Const qualifiers, remember that it is a relatively new feature. There is very little documentation to tell us what is "wrong" or "right", so generally it will take some experimenting. If you feel that it does something it shouldn't do (or doesn't do something it should), by all means report it on the forum! If it is seen as a problem by anyone else, submit a bug report. In general, however, it should work exactly as I've said and all the examples given should do as I say they will (compile if I say they will, not compile if I say they won't). One very important thing to remember, of course, is that they aren't in the latest official release - you must have the latest SVN release for them to work (if the compiler gives an error about one of the examples given here that I told you will compile, then you'll know you need a newer version).
If you have any other difficulties with Const qualifiers, remember that even though there's no documentation for them there are plenty of people on the forum who know about and understand them, and can help you with any questions you may have.
If you still don't understand Const qualifiers, you probably are a newbie who doesn't know much about scope yet anyways - and that's fine, you'll learn as you go. Eventually some decent documentation for this feature will be created, but until then this is all you have. Bear in mind: if you don't understand how they work, you probably won't need them. I for one have written fine programs long before they were around, and I'll probably continue to do so without using them anywhere they aren't needed. There are specific instances when they're useful, and if you understand those instances then you may as well use them when those instances arise. But if you don't understand, that's fine!
Finally, here are some links that should be helpful. The first is a C++ documentation page about Const qualifiers in C++ - of course, it only makes sense if you understand C++, and they also talk about things we don't have yet (i.e., Const methods). Nevertheless it's a fine place to start if you know any C++, so check it out if you like. There is also a link to a forum topic in which I asked about FreeBasic development (and learned about Const qualifiers), and a link to the original SourceForge Feature Request page in which Const qualifiers were originally requested as a feature:
http://www.parashift.com/c++-faq-lite/const-correctness.html
http://www.freebasic.net/forum/viewtopic.php?t=9975&postdays;=0&postorder;=asc&start;=0
http://sourceforge.net/tracker/index.php?func=detail&aid;=1480621&group;_id=122342&atid;=693199
Also note my cliche title, which I chose because of it's clicheness.
What the heck are Const qualifiers? Const qualifiers are a feature recently added to the language (fbc 0.18.3); they're a standard part of C++ and now they exist in FreeBasic too. Const qualifiers are yet another form of protection - they allow some "variables" to act like constants to certain parts of your program, in other words some parts of the program are allowed to access (read) them but not modify them. Just another kind of type safety, really, but an extremely useful one. In particular, they are very useful in OO situations, but you can probably benefit from them to some degree even if you aren't interested in OOP.
The Const qualifier in FreeBasic is essentially an extension to data type declarations, and they may be used with Dim, UDT members, and procedure parameters. Generally you put it right after the "As" part of the variables's data type declaration:
Dim As Const Integer my_const_int = 5
(By the way, throughout this tutorial I use only Integers and Integer Ptrs as examples - however, Const qualifiers should work the same way with all other variable types, including Types, Enums, and anything else that declares something. If for any reason it doesn't, it's probably a bug and you should report it.)
Note in this case we are allowed to change it once - when we create it. But after that, you may not change it any more. In fact, you must initialize it - the compiler will give an error if you don't (interestingly, you are allowed to set it equal to "Any", in which the contents are not guaranteed and could be anything). But you may not do anything that modifies it after that. It will actually give you an error if, for example, you try to do something like this:
my_const_int = 3
Yet, since this doesn't change the variable any, you can do
Print my_const_int
Now this is all very good, but it doesn't seem much different from the normal usage of Const. That is, the following two lines so far seem to mean, for all practical purposes, the same thing:
Dim As Const Integer my_const_int = 5
Const my_int As Integer = 5
Const my_int As Integer = 5
Do they? Not quite. You see, the Const qualifier allows you to create consts that act as variables except that they can't be modified. That means you can put them inside Types and other places. What's more, you can put them inside Sub/Function declarations - and this is a very key reason for their existence:
Sub my_sub (some_num As Integer)
End Sub
End Sub
Normally functions are allowed to modify the variables you send to them. Of course, whether they modify the original variable or just a local copy of the variable depends on whether you use ByVal or ByRef (and of course pointers is a whole different things altogether), but they normally are allowed to modify a variable. This may be undesirable, for whatever reason, and the Const qualifier exists to prevent that. In the function given above, some_num can be modified by the function. Normally it would only be a local copy that is modified, which is fine, since it won't affect the original Const Integer, but what if we declare the function like this?
Sub my_sub (ByRef some_num As Integer)
End Sub
End Sub
Now my_sub has direct access to whatever variable you pass to it, and for that reason you are not allowed to do this sort of thing
my_sub(my_const_int)
Why? Simply because the function may modify the variable. We don't know for sure that it will, of course, but it might, so we can't do that. In fact, the error you'll get if you try to compile that is "Invalid assignment/conversion." It's almost as if the Const Integer is a different variable type, but only when it's ByRef. In that case, it would act like trying to pass a string to an integer argument (or vice-versa). Yet if it's not passed ByRef, we don't have a problem, since there's no way the function can possibly modify the variable!
And of course, if we did something like this:
Sub my_sub (ByRef some_num As Const Integer)
End Sub
End Sub
Then it compiles just fine, but if you try to do the following within the function, you get an error:
some_num = 3
Why? Once again, the original variable has been passed ByRef to the sub. It's now in local scope, but because it's ByRef, any modifications to the variable would modify the original, which cannot be done. Once again, it's entirely possible to create a copy of the variable and modify it all you want:
Dim As Integer copy_of_some_num = some_num
copy_of_some_num = 3
copy_of_some_num = 3
But you can't modify some_num itself!
Now we come to pointers. What about them? For pointers it's a bit more complicated; it's possible to declare the pointer itself as Const, OR what the pointer points to - or even BOTH! So all of the following are valid:
Declare Sub my_sub_a (ByRef ptr_A As Const Byte Ptr)
Declare Sub my_sub_b (ByRef ptr_B As Byte Const Ptr)
Declare Sub my_sub_c (ByRef ptr_C As Const Byte Const Ptr)
Declare Sub my_sub_b (ByRef ptr_B As Byte Const Ptr)
Declare Sub my_sub_c (ByRef ptr_C As Const Byte Const Ptr)
The first one makes it so you can change the pointer itself all you want, but not the data that the pointer points to (even if you change *what* the pointer points to). The second allows you to change what the pointer points to, but you can't make it point to anything else. The third won't let you change what the pointer points to OR the pointer itself! In all cases you can make a copy of the pointer - but it must be a Const Integer Ptr or a Const Integer Const Ptr since otherwise you would be able to change the contents of whatever the original pointer points to! This is great protection against anything being modified!
In case the behaviour of the Const qualifier seems a bit strange to you, I'll explain exactly how it decides what's safe to allow and what isn't. It can actually be summed up pretty quickly: The Const qualifier aims to protect the original data. It doesn't care if you make a copy of the data, or change that copy, it just doesn't want you to be able to change the original data. Remembering this will help you a great deal. Of course, it needs to know what the original data is, which is why when there's pointers involved there are so many different places to put the Const qualifier (and you can even put it in twice - or more, depending on how many pointers there are!) So long as you remember what the Const qualifier is for, you'll never have any difficulty figuring out where to put it - or even if you need it at all (or if you need to not use it).
You can also use the Const qualifier in UDTs. In fact, it's actually a very important thing to OOP (in a similar fashion to Namespaces, which while not a direct part of OOP nevertheless are very much related) - but even if you don't use OOP you can still use Const qualifiers in your Types. I don't even really need to show you an example, as it's pretty obvious by now how it works, but here's an example for you:
Type my_type
As Const Integer t_int= 5
End Type
Dim As my_type t
t.t_int = 3
As Const Integer t_int= 5
End Type
Dim As my_type t
t.t_int = 3
And obviously this won't compile, since the member t_int is Const. Furthermore, you can also declare the variable of that type (in this case, t) with the Const qualifier. The following will not compile either, since ALL members of t are Const:
Type my_type
As Integer t_int= 5
End Type
Dim As Const my_type t
t.t_int = 3
As Integer t_int= 5
End Type
Dim As Const my_type t
t.t_int = 3
As for the OOP side of things (and if you aren't interested in OOP you can skip this part) - you may be wondering about methods. Methods implicitly pass the object ByRef as this when called. Is there a way to create constant objects? Of course! We've already seen that. But some object methods will modify the object, and some won't. Is there a distinction? The answer is yes. As of November 23, 2007, we now have Const procs. That means you can do this:
Type my_object
Public:
Declare Sub modifier_sub ()
'Subs that do not modify the object are declared Const...
Declare Const Sub non_modifier_sub ()
Private:
some_num As Integer = 3
End Type
Sub my_object.modifier_sub ()
this.some_num = 3
End Sub
Sub my_object.non_modifier_sub()
Print this.some_num
End Sub
'Note that only Const objects must be initialized (though in this case the non-Const object will also be),
'just like variables. Thus, you must either have a Constructor for the object, or else you must give all variables
'default initial values (as I did here), in which case the compiler makes a default constructor for you.
Dim As Const my_object t = my_object
Dim As my_object u
'Both of these will compile:
t.non_modifier_sub()
u.non_modifier_sub()
'...but the first of these will not compile, since non-Const methods of Const objects may not be called!
t.modifier_sub()
u.modifier_sub()
'Sleep so we can see the results
Sleep
Public:
Declare Sub modifier_sub ()
'Subs that do not modify the object are declared Const...
Declare Const Sub non_modifier_sub ()
Private:
some_num As Integer = 3
End Type
Sub my_object.modifier_sub ()
this.some_num = 3
End Sub
Sub my_object.non_modifier_sub()
Print this.some_num
End Sub
'Note that only Const objects must be initialized (though in this case the non-Const object will also be),
'just like variables. Thus, you must either have a Constructor for the object, or else you must give all variables
'default initial values (as I did here), in which case the compiler makes a default constructor for you.
Dim As Const my_object t = my_object
Dim As my_object u
'Both of these will compile:
t.non_modifier_sub()
u.non_modifier_sub()
'...but the first of these will not compile, since non-Const methods of Const objects may not be called!
t.modifier_sub()
u.modifier_sub()
'Sleep so we can see the results
Sleep
Once again, the way this works is based on the simple rule. Since the implicitly passed copy of this is passed ByRef, any method is normally able to modify the contents of the object - and if the object is declared As Const, that's not supposed to happen! Thus, there are essentially two kinds of method. The two kinds are given names in the C++ documentation page (listed below in the references): there are mutators and inspectors. Mutators may modify objects, but inspectors do not. Thus, for objects declared As Const, only the inspector methods for those objects may be used - while all methods may be called for non-Const objects. The inspector methods are, of course, the ones declared as Const methods. Thus, for Const objects only their Const methods may be used.
This is all very good, but some of you may be asking - Why do I need this? Well, a direct answer would be out of the *scope* (heh) of this tutorial, so I'll counter - why do we need scope at all? The reason for Const qualifiers (and the future Const methods) is the same as the reason for scope within procedures and modules, and the same reason for hiding of variables in objects: because we want to be certain that something won't unexpectedly change in the middle of the program, when we least expect it. Sometimes we want things to change, and that's when we don't use the Const qualifier. But when you want something to stay what it is, you use the Const qualifier, and you can be certain it will not change (and the compiler won't compile the code if there is danger of it happening!) This is the definition of Const, how it works, and it's the reason you use it! And in general, it's the reason you use any scoping control or data hiding.
Some final notes
If you use Const qualifiers, remember that it is a relatively new feature. There is very little documentation to tell us what is "wrong" or "right", so generally it will take some experimenting. If you feel that it does something it shouldn't do (or doesn't do something it should), by all means report it on the forum! If it is seen as a problem by anyone else, submit a bug report. In general, however, it should work exactly as I've said and all the examples given should do as I say they will (compile if I say they will, not compile if I say they won't). One very important thing to remember, of course, is that they aren't in the latest official release - you must have the latest SVN release for them to work (if the compiler gives an error about one of the examples given here that I told you will compile, then you'll know you need a newer version).
If you have any other difficulties with Const qualifiers, remember that even though there's no documentation for them there are plenty of people on the forum who know about and understand them, and can help you with any questions you may have.
If you still don't understand Const qualifiers, you probably are a newbie who doesn't know much about scope yet anyways - and that's fine, you'll learn as you go. Eventually some decent documentation for this feature will be created, but until then this is all you have. Bear in mind: if you don't understand how they work, you probably won't need them. I for one have written fine programs long before they were around, and I'll probably continue to do so without using them anywhere they aren't needed. There are specific instances when they're useful, and if you understand those instances then you may as well use them when those instances arise. But if you don't understand, that's fine!
Finally, here are some links that should be helpful. The first is a C++ documentation page about Const qualifiers in C++ - of course, it only makes sense if you understand C++, and they also talk about things we don't have yet (i.e., Const methods). Nevertheless it's a fine place to start if you know any C++, so check it out if you like. There is also a link to a forum topic in which I asked about FreeBasic development (and learned about Const qualifiers), and a link to the original SourceForge Feature Request page in which Const qualifiers were originally requested as a feature:
http://www.parashift.com/c++-faq-lite/const-correctness.html
http://www.freebasic.net/forum/viewtopic.php?t=9975&postdays;=0&postorder;=asc&start;=0
http://sourceforge.net/tracker/index.php?func=detail&aid;=1480621&group;_id=122342&atid;=693199