www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Meaning of const variables

reply jmh530 <john.michael.hall gmail.com> writes:
I feel like I have a reasonable understanding of when to use 
const as a parameter in a function or for const member functions. 
However, I don't really understand why/when it should be used as 
a type modifier.

For instance, the Programming in D book basically just says 
(http://ddili.org/ders/d.en/const_and_immutable.html) that a 
const variable is the same as an immutable variable, but you 
should just use immutable.

The D spec uses slightly different terminology 
(https://dlang.org/spec/const3.html), I think. I think the 
storage class stuff is similar to what the PiD book says about 
const parameters, while const types further below matches up with 
what the book says about const variables.

So the line from the spec is
"Const types are like immutable types, except that const forms a 
read-only view of data. Other aliases to that same data may 
change it at any time."

I tried making an alias of a const variable and modifying it, but 
that didn't work. So presumably they mean something else. The 
only way I was able to get any kind of meaningful difference is 
that for normal pointers and immutable pointers, I would get 
errors if trying to get the address of non-normal or 
non-immutable variables, respectively. By contrast, I could 
assign an address of any of them (normal, const, immutable) to a 
pointer to a constant variable.

Not sure when I would use this property, or if there are any 
other relevant differences.

void main()
{
	int x1 = 1;
	const(int) x2 = 1;
	immutable(int) x3 = 1;
	
	int* p1_1 = &x1;
	//int* p1_2 = &x2;
	//int* p1_3 = &x3;
	
	const(int)* p2_1 = &x1;
	const(int)* p2_2 = &x2;
	const(int)* p2_3 = &x2;
	
	//immutable(int)* p3_1 = &x1;
	//immutable(int)* p3_2 = &x2;
	immutable(int)* p3_3 = &x3;
	
	p1_1++;
	//p1_2++;
	//p1_3++;
	p2_1++;
	p2_2++;
	p2_3++;
	//p3_1++;
	//p3_2++;
	p3_3++;
}
Jun 20 2016
next sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Tuesday, 21 June 2016 at 02:24:03 UTC, jmh530 wrote:
 So the line from the spec is
 "Const types are like immutable types, except that const forms 
 a read-only view of data. Other aliases to that same data may 
 change it at any time."

 I tried making an alias of a const variable and modifying it, 
 but that didn't work. So presumably they mean something else.
yep, the spec was talking about data aliases in compiler terminology, not about D aliases. i.e. this is valid: int a; ... const(int)* pa = &a; but this is not: int a; ... immutable(int)* pa = &a; here, in former case we see `pa` as "alias to data". so, what that quote essentially means is: "`const` pointers can point to mutable data, but `immutable` pointers cannot". also, remember that slices are just "pointer, length" pairs, so when we are talking about "pointers" here, the same applies to slices too.
Jun 20 2016
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 21 June 2016 at 02:54:06 UTC, ketmar wrote:

Thanks to you and the others for the detailed clarifications. I 
think part of the difficulty is that there are basically three 
things being discussed that are all very similar: an alias to 
data, a pointer to data, and a view of data.

 so, what that quote essentially means is: "`const` pointers can 
 point to mutable data, but `immutable` pointers cannot".
So an immutable pointer guarantees that whatever it is pointing to will never change, but a const pointer can also point to mutable data, which might change some other way.
Jun 21 2016
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 06/21/2016 06:48 AM, jmh530 wrote:

 So an immutable pointer guarantees that whatever it is pointing to will
 never change,
Yes but saying "_requires_ that data never changes" is more correct. When it comes to a pointer (i.e. the user of data), "guarantee" is related to const.
 but a const pointer can also point to mutable data, which
 might change some other way.
Yes, const means "_I_ will not change." const and immutable are not interesting unless there are references. Otherwise, who cares whether data was copied from const or immutable? const c = 42; immutable i = 43; int a = c; int b = i; a and b are copied from const and immutable, respectively, but we don't care. (Except when they have members or members of members that are references.) const and immutable are interesting when there are references, of which, function parameters make the issue clear to me: void foo(ref const int c, ref immutable int i) { // ... } foo says "I guarantee that I will not modify your data, which I'm referring to as c" and "I require that nobody will modify your data, which I'm referring to as i". So, the const-immutable distinction of a reference parameter is what makes it easy to understand to me. As I observe in that chapter[1] and later in the book[2], it's not always clear which one to use. For example, although const seems more usable, the fact that it has to work with mutable and immutable necessarily "erases" the actual mutability[1]. Another example is with constructor parameters where immutable is potentially faster[2]. That chapter does not really explain why so here is an example: struct S { string fileName; // Needs immutable // Takes as const reference, presumably to be more useful this(const char[] fileName) { // Since const does not guarantee immutability, must take a copy import std.conv; this.fileName = fileName.to!string; } } void main() { // The copy in the constructor is unnecessary in this case because here // the string is already immutable. S's constructor could not know that so // it had to take a copy. auto s = S("my_file"); } The same issue applies for other functions where a reference needs to be kept longer for later use. Ali [1] http://ddili.org/ders/d.en/const_and_immutable.html#ix_const_and_immutable.parameter,%20const%20vs.%20immutable [2] http://ddili.org/ders/d.en/special_functions.html (Search for the section "Immutability of constructor parameters".)
Jun 21 2016
parent jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 21 June 2016 at 15:21:01 UTC, Ali Çehreli wrote:
 [snip]
Thanks.
Jun 21 2016
prev sibling parent "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Tue, Jun 21, 2016 at 01:48:27PM +0000, jmh530 via Digitalmars-d-learn wrote:
[...]
 Thanks to you and the others for the detailed clarifications. I think
 part of the difficulty is that there are basically three things being
 discussed that are all very similar: an alias to data, a pointer to
 data, and a view of data.
The word "alias" is unfortunately overloaded to mean similar, but not exactly the same, things. "Alias" as used in the keyword "alias" is a compiler construct that has no real bearing on the type system. All it is, is to create a new name for something. For example, if I have: struct S { int x; } alias y = S.x; all this means is that whenever the compiler sees "y" it should act as if it has seen "S.x" instead. Well, not exactly, but basically y will refer to whatever S.x refers to in the scope of the declaration. This makes it useful sometimes for naming things that are hard or impossible to name in a different scope, for example: struct S(T) { alias ElementType = T; } S!int s; static assert(is(s.ElementType == int));
From an outsider's POV, the template parameter T is not easily obtained,
but having a convenient alias inside the scope where it *is* easily obtained, makes it readily available to outside code. None of this is related to the present topic, though. Which brings us to the second meaning of "alias", as used in the type system to mean that the same data can be reached from multiple references. At the bottom of this is pointers, and abstractions built on top of pointers. int x; int* p = &x; This sense of "alias" is usually used as a verb, as in "the pointer p aliases the variable x". Meaning, you can reach the data represented by x via the pointer p. So this is similar to the first meaning of "alias" in the sense that p is kinda like a different name for x, but semantically it's not quite the same thing, because alias in the first sense is a compile-time concept of having one identifier being treated as though it were another, whereas alias in the second sense here is a runtime concept, in that the data x exists somewhere in memory, say location L, and the pointer p happens to contain the value L, so using p we can find x in memory without actually referring to x directly in the code. Now, what's the distinction between int*, const(int)*, and immutable(int)*? Or, for that matter, const(int*) and immutable(int*)? To understand this, it's useful to think of the data being pointed to distinctly from the pointer. All of the above pointers point to some int sitting somewhere in memory. Let's say there are two ints, sitting in memory locations L1 and L2, respectively. When we declared the two ints, we specified the first one as simply 'int', meaning it's mutable. So memory location L1 contains an int that can be modified. Let's say the second int is declared as immutable(int), so L2 contains an int that, once initialized, cannot be modified. (In this respect, there is no difference if we declared const(int) instead, since either way nobody can modify the value once it's initialized.) Now, int* is a pointer to (mutable) int, so it can never point to L2. If the language allowed that, it would break the contract that L2 cannot be modified. Similarly, immutable(int)* is a pointer to immutable(int), so it can never point to L1. However, const(int)* can point to both L1 and L2, because const(int)* just means that you cannot use *this* pointer to modify the data, but somebody else who has a mutable reference (i.e., int*) pointing to the same data, may use it to modify the data. So in short: int* means "pointer to an int that anybody can modify" immutable(int)* means "pointer to an int that nobody can modify" const(int)* means "pointer that cannot be used to modify the int, but can be used to read the int" So if you have an int* in hand, you know that the memory location it points to is mutable, and if you have an immutable(int)* in hand, you know that the memory location it points to is immutable (to everybody, not just you). However, if you have a const(int)* in hand, all you know is that *you* can't modify the data (or more precisely, you cannot use this particular pointer to modify the data), but it says nothing about whether the data itself is actually modifiable or not. What about const(int*) and immutable(int*)? Here, thanks to transitive const in D, the const and immutable applies both to the pointer and the data pointed to. So const(int*) means that the data cannot be modified through this pointer, and also that you cannot modify this pointer itself. (But somebody with a mutable reference to this pointer may change what it points to.) Similarly, immutable(int*) means that the data cannot be modified by anybody (whether through this pointer or otherwise), and the pointer itself also cannot be modified by anybody. T -- Many open minds should be closed for repairs. -- K5 user
Jun 21 2016
prev sibling next sibling parent "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Tue, Jun 21, 2016 at 02:24:03AM +0000, jmh530 via Digitalmars-d-learn wrote:
 I feel like I have a reasonable understanding of when to use const as
 a parameter in a function or for const member functions. However, I
 don't really understand why/when it should be used as a type modifier.
 
 For instance, the Programming in D book basically just says
 (http://ddili.org/ders/d.en/const_and_immutable.html) that a const
 variable is the same as an immutable variable, but you should just use
 immutable.
I think that's an oversimplification. :-) Here's a diagram that helped me understand what the deal with const/immutable is: const / \ (mutable) immutable This is a kind of "type inheritance diagram" analogous to a class inheritance diagram. What it means is that mutable and immutable cannot implicitly convert to each other (except for atomic by-value types), but both mutable and immutable can implicitly convert to const. Why? Because const means "you are not allowed to modify this value" (but somebody else may). Converting mutable to const, which usually happens in function calls, simply means the callee is bound to a contract never to modify the value, but the caller may have a mutable reference to the same value and may legally modify it. OTOH, immutable means "NOBODY is allowed to modify this value". Meaning that the value is truly, physically immutable; neither caller nor callee nor any 3rd party may modify its value. This is actually a strong guarantee because the compiler can statically verify that nobody actually modifies the value; therefore certain optimizations can be made (e.g., storing it in ROM, in an immutable data segment, eliding multiple reads from the variable because it is guaranteed that its value never changes no matter what else happens in between). Such optimizations cannot be applied to const, because we aren't guaranteed that a 3rd party won't change the value after we last read it. Const only means *we* cannot change it. So const is a weaker form of immutable... its main purpose is to reduce redundancy in functions that can work with both mutable and immutable data. For example, a function that only reads the value of a parameter can be passed an immutable value safely -- since it won't modify it -- and can also take a mutable value -- even though modification is valid, the function doesn't modify it anyway. This means we can pass both a mutable value and an immutable value to the same function and it's fine, as long as the function itself never changes anything. But we can't write the function as: auto func(immutable X x) { ... } because converting mutable to immutable is illegal, so we couldn't call the function with mutable arguments. Obviously we also can't write the function without type qualifiers: auto func(X x) { ... } because immutable cannot convert to mutable, so we can't pass an immutable value to it. The solution is to use const: auto func(const(X) x) { ... } Const ensures that func doesn't attempt to modify an immutable value when we give it one, but since const doesn't guarantee a 3rd party won't modify the value, we can also legally pass a mutable value to it. Since func is bound by const never to modify x, it doesn't matter. Without const, we'd have to write two identical functions, one that takes an immutable value, and one that takes a mutable value. Const lets us unify the two, which is valid since const ensures that func doesn't do any funny business behind our backs by trying to modify its arguments. Of course, this is only part of the story. The other part is that if a value has a by-value type, then it's OK to implicitly convert to/from immutable: because you're always making a copy of the by-value type. You can't convert a mutable *reference* to an immutable reference or vice versa (otherwise you could use the mutable reference to break immutability), but you *can* copy the value itself from mutable to immutable and vice versa. So a function that takes an int argument can be legally passed an immutable int -- since the function gets a copy of the immutable value, and it's OK to modify the copy instead of the original value. With this in view, it would make sense that const would usually be used in function parameters, since that is where it's most useful, whereas immutable would normally be used in variables that you wish to initialize once and have its value never change again thereafter. This is probably where that advice came from that you should use immutable variables rather than const variables. Still, there *are* times when const variables make sense -- this applies when iterating read-only over a collection of objects, for example. The iterator would need to be const so that it can refer to both mutable and immutable members of the collection, again so that you don't have to copy-n-paste the same code, one in a function that takes an immutable collection and another that takes a mutable collection. Hope this helps. T -- Talk is cheap. Whining is actually free. -- Lars Wirzenius
Jun 20 2016
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Tuesday, 21 June 2016 at 02:24:03 UTC, jmh530 wrote:

 I tried making an alias of a const variable and modifying it, 
 but that didn't work. So presumably they mean something else.
By alias they mean another reference (or view) to the data, as in Ketmar's example and your assignment of a &x1 to p2_1. No one can modify the value of x1 through p2_1 because it's const, but it's still possible to modify x1 directly. This is most obvious with reference types like pointers, classes and arrays. In this case, it's possible for the data and the view to the data to be separate. int x; // The data (and one view to it) int *px = &x; // A second view to the same data For value types, there's effectively no difference between const and immutable, because a value type is both a view to the data *and* the data itself. If it is const or immutable, it's impossible to have any mutable view to the same data e.g.: const cx = 1; immutable ix = 2; int* pcx = &cx; // No Can do int* pix = &ix; // Ditto In this case, you might as well go with immutable, since the behavior is identical. And really, if you never need to take the address of the variable, then a manifest constant using enum would be more appropriate. The same holds true for any reference types that you can initialize with only one view, such as an array or a class (though you wouldn't want to use a manifest constant with these most of the time). const(int)[] ints1 = [0, 1, 2, 3, 4]; const(int[]) ints2[0, 1, 2, 3, 4]; In both of these cases, the original view of the data (the array literal) is temporary. You can't change the values at any index in these arrays once they are initialized because it is not possible to take a mutable view of the data. May as well go ahead and use immutable here. I think that's what Ali was getting at in PiD. IIRC, I said the same thing in Learning D.
Jun 21 2016
parent Mike Parker <aldacron gmail.com> writes:
 behavior is identical. And really, if you never need to take 
 the address of the variable, then a manifest constant using 
 enum would be more appropriate.
Actually, I should say it *may* be more appropriate. Definitely only when the initializer is known at compile time. There are cases when you initialize a local immutable variable with a runtime value.
Jun 21 2016