www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - const, in and final

reply Arcane Jill <Arcane_member pathlink.com> writes:
I have given a lot of thought to the whole "const" thing, and it seems to me
that there is a way around this that will keep everybody happy. (It was not I
who thought of this, by the way - I'm just clarifying).

Summary - the story so far:
Three uses of const have been identified in C++
(1) To declare a complile-time constant
(2) To declare an instance of something which needs to be stored in ROM
(3) As part of Design-By-Contract, to specify a contract that a function will
not modify something

The keyword "const" is implemented in D to allow use (1) only.

It has been suggested that the distinction between (1) and (2) could be decided
by the compiler. Alas, that may not be so. Given the declaration:

       const int FORTY_TWO = 42;
the expression (&FORTY_TWO) should be illegal in case (1), but legal in case (2). Therefore, if we wish the language to support option (2), we need a need a new keyword. I suggest re-using "final". Thus:
       const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot
take address
       final int FORTY_TWO = 42;       // Stored in ROM, so address can be
taken, but value may not be changed
This brings us to the can of worms that is definition (3). I believe we can do this. I believe it can work. Here's how: The CURRENT situation is that function parameters may be declared in three different ways. These are: (i) in (the default) (ii) out (iii) inout To implement DbC-readonly assertions, we actually need FOUR states. For consistency, these should be: (i) (default) (ii) in (iii) out (iv) inout (default) is what you get if you specify none of the other keywords. For DbC-readonly to work, we only need to state that out, inout, and default, shall work exactly as before (current behavior is unchanged), but new behavior needs to be defined for anything declared using "in". I shall describe this new behavior below. Note though that in addition to its placement before formal function paramters, "in" must also be placeable before member function declarations, like this:
       class A
       {
           in void f()         // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       }
For reference, the C++ syntax for doing this is:
       class A
       {
           void f() const      // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       };
Above, I talked about a difference in behavior. The important question is HOW this is going to work? Specifically, what gets passed where? How can the compiler spot a contract violation? These questions I shall answer, now. To the first point, parameters are passed exactly as they are now. Current behavior is suffient. To the second point, given the declaration:
       void f(in SomeObject a)
the following would all be compile-time errors if they occur within the body of the function: (1) a = expression; // illegal always (2) a.m = expression; // illegal always (3) a.g(); // illegal unless g() was declared as in void g() (4) g(a) // illegal unless g() was declared as void g(in SomeObject a) These tests are sufficient to catch all contract violations at compile-time. (The same tests could also be used to verify that "final" objects are not modified). Incidently, the statement:
       this = expression;
should /always/ be illegal, regardless of whether or not the function was declared with "in". Note that currently, that statement is allowed, The consequences of executing it are undefined. CONSEQUENCES If we were starting from scratch, there would be no consequences. But we are not starting from scratch. We already have a large body of code out there, much of which will not immediately compile under this proposed change. There is an immediate consequence for getter functions (properties). All property getter functions would have to be redclared, like this:
       int x() { return x_; }      // BEFORE
       in int x() { return x_; }   // AFTER
Similarly, there is an immediate consequence for the non-assigning operator overloads, which would also have to be redeclared:
       SomeObject opAdd(SomeObject x)          // BEFORE
       in SomeObject opAdd(in SomeObject x)    // AFTER
Then, similarly, you redeclare every other function that requires it, in every file, least-dependent (that is, most low-level) file first. The absolute simplest way to do this is to declare EVERYTHING that isn't "out" or "inout" as "in", and then remove the "in" keyword from things that won't compile. Providing you attack the source code in the right order, this should convert everything that needs it. And it might even show up a few bugs that got missed along the way! CONCLUSION This is achieveable, and simple. The remaining questions are: (i) does the D community want this, given that recoding of a lot of source files will be necessary, and (ii) how does Walter feel about implementing it, given that the illegalities would be relatively easy to spot? OH YEAH - I FORGOT Given the declaration:
       f(out SomeType x)
I would *assume* (though I haven't done the experiment) that either the caller or the callee overwrites any former value of x with the init value of SomeType, before doing anything with x. If it doesn't, it should. Arcane Jill
May 27 2004
next sibling parent reply Hauke Duden <H.NS.Duden gmx.net> writes:
Arcane Jill wrote:
 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function will
 not modify something
 
 The keyword "const" is implemented in D to allow use (1) only.
 
 It has been suggested that the distinction between (1) and (2) could be decided
 by the compiler. Alas, that may not be so. Given the declaration:
 
 
      const int FORTY_TWO = 42;
the expression (&FORTY_TWO) should be illegal in case (1), but legal in case (2). Therefore, if we wish the language to support option (2), we need a need a new keyword.
Why? const int FORTY_TWO=42; means that ist an integer with the value 42 which will never change. Thus &FORTY_TWO is perfectly legal. The distrinction you're talking about is an optimization. When seeing FORTY_TWO being used as a value then the compiler knows that it can safely insert the constant 42 instead. And if the address of FORTY_TWO is NEVER needed then it may even decide to axe the integer object completely. But, this is all transparent to the programmer and completely done behind the scenes by the compiler. So I fail to see the problem. Hauke
May 27 2004
parent reply Arcane Jill <Arcane_member pathlink.com> writes:
 the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
 (2). Therefore, if we wish the language to support option (2), we need a need a
 new keyword. 
Why? const int FORTY_TWO=42; means that ist an integer with the value 42 which will never change. Thus &FORTY_TWO is perfectly legal. The distrinction you're talking about is an optimization. When seeing FORTY_TWO being used as a value then the compiler knows that it can safely insert the constant 42 instead. And if the address of FORTY_TWO is NEVER needed then it may even decide to axe the integer object completely. But, this is all transparent to the programmer and completely done behind the scenes by the compiler. So I fail to see the problem. Hauke
The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, but the expression "&FORTY_TWO" occurs in module B. When compiling module A, how is the compiler to know (without clairvoyance) what module B is going to do? It can't optimize FORTY_TWO away if there's even a /chance/ that some other module might take its address. Yet ... we want it to make that optimization. Don't we? Jill
May 27 2004
parent reply Hauke Duden <H.NS.Duden gmx.net> writes:
Arcane Jill wrote:
the expression (&FORTY_TWO) should be illegal in case (1), but legal in case
(2). Therefore, if we wish the language to support option (2), we need a need a
new keyword. 
Why? const int FORTY_TWO=42; means that ist an integer with the value 42 which will never change. Thus &FORTY_TWO is perfectly legal. The distrinction you're talking about is an optimization. When seeing FORTY_TWO being used as a value then the compiler knows that it can safely insert the constant 42 instead. And if the address of FORTY_TWO is NEVER needed then it may even decide to axe the integer object completely. But, this is all transparent to the programmer and completely done behind the scenes by the compiler. So I fail to see the problem. Hauke
The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, but the expression "&FORTY_TWO" occurs in module B. When compiling module A, how is the compiler to know (without clairvoyance) what module B is going to do? It can't optimize FORTY_TWO away if there's even a /chance/ that some other module might take its address. Yet ... we want it to make that optimization. Don't we?
I always thought this kind of optimization (throwing away unused entities) is done by the linker. Isn't it? Anyway, I don't think that having some additional integers floating around would be any problem. The additional overhead certainly does not justify introducing two different kinds of const keywords. And if it is a problem for a specialized application you could still let the compiler compile all modules at once. For me this smells like the "register" keyword all over again. Hauke
May 27 2004
parent reply Kevin Bealer <Kevin_member pathlink.com> writes:
In article <c94nff$e11$1 digitaldaemon.com>, Hauke Duden says...
..
 The problem occurs if "const int FORTY_TWO = 42;" occurs in module A, but the
 expression "&FORTY_TWO" occurs in module B. When compiling module A, how is the
 compiler to know (without clairvoyance) what module B is going to do? It can't
 optimize FORTY_TWO away if there's even a /chance/ that some other module might
 take its address. Yet ... we want it to make that optimization. Don't we?
I always thought this kind of optimization (throwing away unused entities) is done by the linker. Isn't it? Anyway, I don't think that having some additional integers floating around would be any problem. The additional overhead certainly does not justify introducing two different kinds of const keywords. And if it is a problem for a specialized application you could still let the compiler compile all modules at once. For me this smells like the "register" keyword all over again. Hauke
In C++ at least, the linker throws away compilation units (files) when no references to them are found. Anything smaller than that is probably not removed by the linker. I spent all day today (at work) moving stuff between compilation units to reduce executable sizes. Compiler experts: What do you think of breaking compilation units up into the smallest possible pieces in the compiler? If the compiler emitted one .o file for each function or method (optionally) and automatically bound them into a .a file for me, I would only pull in methods that were actually called; the recursive effect of this could reduce file sizes massively. I reduced a C++ executable by 30MB by doing this by hand, and I only removed dependency on one library, so I think it could help all-around. (Yes this was debug mode, our release mode binaries are not that large). Kevin
May 27 2004
next sibling parent reply Juan C <Juan_member pathlink.com> writes:
What do you think of breaking compilation units up into the smallest possible
pieces in the compiler?  If the compiler emitted one .o file for each function
or method (optionally) and automatically bound them into a .a file for me, I
would only pull in methods that were actually called; the recursive effect of
this could reduce file sizes massively.
I think this is one of the things I mentioned when I first heard about D. I got into the habit of putting only one function in each file when I worked on a large team using C, and that can be done with C++ too (if I recall correctly). I up over several files. Tiny code files offer several benefits to teams. However, I believe the compiler/linker shouldn't do it _for_ you.
May 28 2004
parent Kevin Bealer <Kevin_member pathlink.com> writes:
In article <c97r1k$1v9u$1 digitaldaemon.com>, Juan C says...
What do you think of breaking compilation units up into the smallest possible
pieces in the compiler?  If the compiler emitted one .o file for each function
or method (optionally) and automatically bound them into a .a file for me, I
would only pull in methods that were actually called; the recursive effect of
this could reduce file sizes massively.
I think this is one of the things I mentioned when I first heard about D. I got into the habit of putting only one function in each file when I worked on a large team using C, and that can be done with C++ too (if I recall correctly). I up over several files. Tiny code files offer several benefits to teams. However, I believe the compiler/linker shouldn't do it _for_ you.
Out of curiosity, why not? Doing this manually takes a lot of effort, and has no benefit, except for the dubious C-language stoicism of having walked the whole distance on foot and not gotten eaten by a bear (yet). What I want is dependency information to be done for "program elements", be they functions, methods, static strings, or global/module scope data objects. Then only the necessary parts of the code will need to be included. The only (functional) downside I see is the inability to write functions that can only be run from GDB via "call x(4)". Kevin
May 28 2004
prev sibling parent reply Arcane Jill <Arcane_member pathlink.com> writes:
In article <c96kgq$651$1 digitaldaemon.com>, Kevin Bealer says...
Compiler experts:
I don't claim to be a compiler expert, but...
What do you think of breaking compilation units up into the smallest possible
pieces in the compiler?  If the compiler emitted one .o file for each function
or method (optionally) and automatically bound them into a .a file for me, I
would only pull in methods that were actually called; the recursive effect of
this could reduce file sizes massively.

I reduced a C++ executable by 30MB by doing this by hand, and I only removed
dependency on one library, so I think it could help all-around.  (Yes this was
debug mode, our release mode binaries are not that large).
..there is a problem doing this in D. The way it's designed. private stuff has to be in the same *file* as anything which references it, and there is no package level attribute. So we can't even implement the same level of granularity to which we are accustomed in C and C++. For this, and other reasons, I am now convinced there is a stong case for the "package" attribute to exist in D. Arcane Jill
May 29 2004
parent Kevin Bealer <Kevin_member pathlink.com> writes:
In article <c99ls1$1lmi$1 digitaldaemon.com>, Arcane Jill says...
In article <c96kgq$651$1 digitaldaemon.com>, Kevin Bealer says...
Compiler experts:
I don't claim to be a compiler expert, but...
What do you think of breaking compilation units up into the smallest possible
pieces in the compiler?  If the compiler emitted one .o file for each function
or method (optionally) and automatically bound them into a .a file for me, I
would only pull in methods that were actually called; the recursive effect of
this could reduce file sizes massively.

I reduced a C++ executable by 30MB by doing this by hand, and I only removed
dependency on one library, so I think it could help all-around.  (Yes this was
debug mode, our release mode binaries are not that large).
..there is a problem doing this in D. The way it's designed. private stuff has to be in the same *file* as anything which references it, and there is no package level attribute. So we can't even implement the same level of granularity to which we are accustomed in C and C++. For this, and other reasons, I am now convinced there is a stong case for the "package" attribute to exist in D. Arcane Jill
I agree, there is a problem with doing it using user-level tools before the compile, but once in the compiler, it could be done in D, C, or C++. Compilers enforce the access attributes at the source level. If it rejects the usage, it produces an error message. If it accepts the usage, then it would still be free to write multiple objects. In C or C++, this change would extend the benefit of using one-file-per-package to all programs, even those that put all code in one file. In D, it may be less beneficial because the virtual table would probably pull in all the .o files, unless the given functions were "final". If you need the benefit in D, you could use heroic measures to get it, by writing classes like this: Module A: class Foo { final uint increase(int Z) { return impl_Foo_increase(x,y,Z); } final uint decrease(int W) { return impl_Foo_decrease(x,y,W); } private: int x,y; }; Module B: uint impl_Foo_increase(inout uint x, inout uint y, inout Z) { .. some code } Module C: uint impl_Foo_decrease(inout uint x, inout uint y, inout W) { .. some code } I know, its a bit ugly. This would also be a way to write C/C++ or C/C++/D programs. (Uh oh -- what have I unleased? Please burn this email!) There are probably people doing this already of course, but without the "final"ity. Kevin
May 30 2004
prev sibling next sibling parent reply "Kris" <someidiot earthlink.dot.dot.dot.net> writes:
"Arcane Jill" wrote
 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function
will
 not modify something
 The keyword "const" is implemented in D to allow use (1) only.
Possibly misleading AJ. From the documentation: ---------------------------- Const means constant Const is not a type modifier in D, it is a storage class. Hence, the value of a const cannot change. A const declaration can be put in read-only storage, and the optimizer can assume its value never changes. This is unlike C/C++, where since const is a type modifier, the value of a reference to a const can legally change. ---------------------------- This would appear to include (2) as well.
 const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot
take address I fail to understand why const is used like this when enum is (apparently) designed explicitly for this purpose. I think this type of const usage should be tossed, along with the Octal prefix everyone loves so much <g>
May 27 2004
parent reply Antti =?iso-8859-1?Q?Syk=E4ri?= <jsykari gamma.hut.fi> writes:
In article <c95ab4$19a1$1 digitaldaemon.com>, Kris wrote:
 
 "Arcane Jill" wrote
 Possibly misleading AJ. From the documentation:
 
 ----------------------------
 Const means constant
 Const is not a type modifier in D, it is a storage class. Hence, the value
 of a const cannot change. A const declaration can be put in read-only
 storage, and the optimizer can assume its value never changes. This is
 unlike C/C++, where since const is a type modifier, the value of a reference
 to a const can legally change.
 ----------------------------
(Um, and I just have to say that the documentation itself seems to be hinting that values of constant declarations cannot be put into read-only memory in C++, which they can. Const _references_ are a different business.)
 This would appear to include (2) as well.
 
 
 const int FORTY_TWO = 42;       // Not stored anywhere, therefore cannot
I fail to understand why const is used like this when enum is (apparently) designed explicitly for this purpose. I think this type of const usage should be tossed, along with the Octal prefix everyone loves so much <g>
Because enums cannot be used to declare constant floats, character literals, other objects or (indeed) have their address taken. Enums, I think, are designed for the purpose of - well - enumerating. :) -Antti -- I will not be using Plan 9 in the creation of weapons of mass destruction to be used by nations other than the US.
May 27 2004
parent reply "Kris" <someidiot earthlink.dot.dot.dot.net> writes:
"Antti Sykäri" wrote
 const int FORTY_TWO = 42;       // Not stored anywhere, therefore
cannot
 I fail to understand why const is used like this when enum is
(apparently)
 designed explicitly for this purpose. I think this type of const usage
 should be tossed, along with the Octal prefix everyone loves so much <g>
Because enums cannot be used to declare constant floats, character literals, other objects or (indeed) have their address taken. Enums, I think, are designed for the purpose of - well - enumerating. :) -Antti
Right :-) I guess I wasn't very clear on that one. That particular example from AJ was *only* for those constants that cannot have an address taken. The "read-only" style of const (2) would hande literals, objects, structs etc. If enum cannot handle floats then perhaps it could? It is legal to set a D enum type, but I guess that covers int and char only? Perhaps I'm being naiive ... - Kris
May 27 2004
parent reply Arcane Jill <Arcane_member pathlink.com> writes:
In article <c95pbn$1vov$1 digitaldaemon.com>, Kris says...

That particular example from AJ was *only* for those constants that cannot
have an address taken.
I did give an example, but it was *only* an example. The underlying point was generic. It wasn't practical to give the whole infinity of possible examples. In general, I tend to give the simplest example I can think of. Perhaps a better example would have been:
       const int[10000] = 
           // loads of data
In any case, it was not I who claimed that this was a problem, I merely repeated it. Another poster (forgive me, I can't remember who) was interested in ROM storage for firmware and stuff. To be honest, this isn't a big deal to me either. By the DbC problem is. Jill
May 27 2004
parent "Kris" <someidiot earthlink.dot.dot.dot.net> writes:
I was the poster vis-a-vis RO segments, but I thought your original post
made the correct distinction AJ: here it is again

Summary - the story so far:
Three uses of const have been identified in C++
(1) To declare a complile-time constant
(2) To declare an instance of something which needs to be stored in ROM
(3) As part of Design-By-Contract, to specify a contract that a function
will
not modify something

I thought you were suggesting (1) should be things you cannot take the
address of? If so, I'd fully agree. I tend to equate (1) with enum, since
they are compile-time constants.

(2) doesn't *need* to be stored in ROM, but it declares the instance as RO
and is therefore a ROM candidate. This, I thought, would have included your
{const int[10000] = ...} as noted below, for which you should be able to
take an address of. However, your post below is in the context of
non-addressable items like (1) ... Did you mean {final int [10000] = }
instead, per your suggested syntax change, or are you saying that (1) should
be addressable?

Confused again;

- Kris


"Arcane Jill" <Arcane_member pathlink.com> wrote in message
news:c95qlo$21ru$1 digitaldaemon.com...
 In article <c95pbn$1vov$1 digitaldaemon.com>, Kris says...

That particular example from AJ was *only* for those constants that
cannot
have an address taken.
I did give an example, but it was *only* an example. The underlying point
was
 generic. It wasn't practical to give the whole infinity of possible
examples. In
 general, I tend to give the simplest example I can think of.

 Perhaps a better example would have been:

       const int[10000] =
           // loads of data
In any case, it was not I who claimed that this was a problem, I merely
repeated
 it. Another poster (forgive me, I can't remember who) was interested in
ROM
 storage for firmware and stuff.

 To be honest, this isn't a big deal to me either. By the DbC problem is.

 Jill
May 27 2004
prev sibling next sibling parent Regan Heath <regan netwin.co.nz> writes:
I think the default behaviour, i.e. that of 'in' *should* be changed.

I think it should:
1- pass by reference (even for structs)
2- enforce what you have mentioned below, compile time checking that the 
variable is not changed.

The reasoning for 1 is that typically you do not want to copy-in a struct, 
doing so for a large struct is in-efficient, and if you want to modify it 
then either:
a- it is incorrectly defined as an 'in' parameter
b- you only want to do so for the functions scope so why not:

struct foo {
   int a;
   int b;
   int c;
}

int foobar(in foo _abc) {
   foo abc = _abc;
}

The reasoning for 2 is an obvious dbc rule, if a variable is not an out or 
inout then it should not get modified by the function.


I also think if you replace the word 'in' below with 'const' it looks like 
what you have said *is* exactly what const is in C/C++..


I agree there needs to be some way of saying that a method of a class does 
not modify it's members, so it can then be used on/with an 'in' parameter, 
why not:

class A {
   void foo(in this, int a) {  //does not modify members of A
   }
   void foo(int a) {  //does modify members of A (or rather does not 
guarantee not to)
   }
}

in other words an *optional* definition for the this parameter.


I realise my proposed change to the behaviour of 'in' will possibly break 
existing code which is expecting the copy-in behaviour of structs.. but I 
believe it is the *right* way to do it, and as D is not set in stone yet..


On Thu, 27 May 2004 08:55:21 +0000 (UTC), Arcane Jill 
<Arcane_member pathlink.com> wrote:
 I have given a lot of thought to the whole "const" thing, and it seems 
 to me
 that there is a way around this that will keep everybody happy. (It was 
 not I
 who thought of this, by the way - I'm just clarifying).

 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function 
 will
 not modify something

 The keyword "const" is implemented in D to allow use (1) only.

 It has been suggested that the distinction between (1) and (2) could be 
 decided
 by the compiler. Alas, that may not be so. Given the declaration:

       const int FORTY_TWO = 42;
the expression (&FORTY_TWO) should be illegal in case (1), but legal in case (2). Therefore, if we wish the language to support option (2), we need a need a new keyword. I suggest re-using "final". Thus:
       const int FORTY_TWO = 42;       // Not stored anywhere, therefore 
 cannot take address
       final int FORTY_TWO = 42;       // Stored in ROM, so address can 
 be taken, but value may not be changed
This brings us to the can of worms that is definition (3). I believe we can do this. I believe it can work. Here's how: The CURRENT situation is that function parameters may be declared in three different ways. These are: (i) in (the default) (ii) out (iii) inout To implement DbC-readonly assertions, we actually need FOUR states. For consistency, these should be: (i) (default) (ii) in (iii) out (iv) inout (default) is what you get if you specify none of the other keywords. For DbC-readonly to work, we only need to state that out, inout, and default, shall work exactly as before (current behavior is unchanged), but new behavior needs to be defined for anything declared using "in". I shall describe this new behavior below. Note though that in addition to its placement before formal function paramters, "in" must also be placeable before member function declarations, like this:
       class A
       {
           in void f()         // a contract that the function will not 
 modify any of A's member variables
           {
               // do stuff
           }
       }
For reference, the C++ syntax for doing this is:
       class A
       {
           void f() const      // a contract that the function will not 
 modify any of A's member variables
           {
               // do stuff
           }
       };
Above, I talked about a difference in behavior. The important question is HOW this is going to work? Specifically, what gets passed where? How can the compiler spot a contract violation? These questions I shall answer, now. To the first point, parameters are passed exactly as they are now. Current behavior is suffient. To the second point, given the declaration:
       void f(in SomeObject a)
the following would all be compile-time errors if they occur within the body of the function: (1) a = expression; // illegal always (2) a.m = expression; // illegal always (3) a.g(); // illegal unless g() was declared as in void g() (4) g(a) // illegal unless g() was declared as void g(in SomeObject a) These tests are sufficient to catch all contract violations at compile-time. (The same tests could also be used to verify that "final" objects are not modified). Incidently, the statement:
       this = expression;
should /always/ be illegal, regardless of whether or not the function was declared with "in". Note that currently, that statement is allowed, The consequences of executing it are undefined. CONSEQUENCES If we were starting from scratch, there would be no consequences. But we are not starting from scratch. We already have a large body of code out there, much of which will not immediately compile under this proposed change. There is an immediate consequence for getter functions (properties). All property getter functions would have to be redclared, like this:
       int x() { return x_; }      // BEFORE
       in int x() { return x_; }   // AFTER
Similarly, there is an immediate consequence for the non-assigning operator overloads, which would also have to be redeclared:
       SomeObject opAdd(SomeObject x)          // BEFORE
       in SomeObject opAdd(in SomeObject x)    // AFTER
Then, similarly, you redeclare every other function that requires it, in every file, least-dependent (that is, most low-level) file first. The absolute simplest way to do this is to declare EVERYTHING that isn't "out" or "inout" as "in", and then remove the "in" keyword from things that won't compile. Providing you attack the source code in the right order, this should convert everything that needs it. And it might even show up a few bugs that got missed along the way! CONCLUSION This is achieveable, and simple. The remaining questions are: (i) does the D community want this, given that recoding of a lot of source files will be necessary, and (ii) how does Walter feel about implementing it, given that the illegalities would be relatively easy to spot? OH YEAH - I FORGOT Given the declaration:
       f(out SomeType x)
I would *assume* (though I haven't done the experiment) that either the caller or the callee overwrites any former value of x with the init value of SomeType, before doing anything with x. If it doesn't, it should. Arcane Jill
-- Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
May 27 2004
prev sibling next sibling parent Antti =?iso-8859-1?Q?Syk=E4ri?= <jsykari gamma.hut.fi> writes:
This proposition has merit. Instead of advocating "const" to the
language (like a lot of people, including myself until I started to
doubt myself, have done) it extends the in/out/inout concept so that it
would be semantically as strong as "const" is in C/C++.

Just adding const would make the language clunky and complicated;
implementing this proposition might be just the right solution.

(There are of course the troubles that come with const as well, I think.
Something about overload resolution or something. Anyway, it's been one
argument against const.)

-Antti

In article <c94adp$20fe$1 digitaldaemon.com>, Arcane Jill wrote:
 I have given a lot of thought to the whole "const" thing, and it seems to me
 that there is a way around this that will keep everybody happy. (It was not I
 who thought of this, by the way - I'm just clarifying).
[...]
 (3) As part of Design-By-Contract, to specify a contract that a function will
 not modify something
[...]
 This brings us to the can of worms that is definition (3). I believe we can do
 this. I believe it can work. Here's how:
 
 The CURRENT situation is that function parameters may be declared in three
 different ways. These are:
 
 (i)   in (the default)
 (ii)  out
 (iii) inout
 
 To implement DbC-readonly assertions, we actually need FOUR states. For
 consistency, these should be:
 
 (i)   (default)
 (ii)  in
 (iii) out
 (iv)  inout
 
 (default) is what you get if you specify none of the other keywords. For
 DbC-readonly to work, we only need to state that out, inout, and
 default, shall work exactly as before (current behavior is unchanged),
 but new behavior needs to be defined for anything declared using "in".
 I shall describe this new behavior below.
 
 Note though that in addition to its placement before formal function paramters,
 "in" must also be placeable before member function declarations, like this:
 
       class A
       {
           in void f()         // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       }
For reference, the C++ syntax for doing this is:
       class A
       {
           void f() const      // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       };
Above, I talked about a difference in behavior. The important question is HOW this is going to work? Specifically, what gets passed where? How can the compiler spot a contract violation? These questions I shall answer, now. To the first point, parameters are passed exactly as they are now. Current behavior is suffient. To the second point, given the declaration:
       void f(in SomeObject a)
the following would all be compile-time errors if they occur within the body of the function: (1) a = expression; // illegal always (2) a.m = expression; // illegal always (3) a.g(); // illegal unless g() was declared as in void g() (4) g(a) // illegal unless g() was declared as void g(in SomeObject a) These tests are sufficient to catch all contract violations at compile-time. (The same tests could also be used to verify that "final" objects are not modified). Incidently, the statement:
       this = expression;
should /always/ be illegal, regardless of whether or not the function was declared with "in". Note that currently, that statement is allowed, The consequences of executing it are undefined. CONSEQUENCES If we were starting from scratch, there would be no consequences. But we are not starting from scratch. We already have a large body of code out there, much of which will not immediately compile under this proposed change. There is an immediate consequence for getter functions (properties). All property getter functions would have to be redclared, like this:
       int x() { return x_; }      // BEFORE
       in int x() { return x_; }   // AFTER
Similarly, there is an immediate consequence for the non-assigning operator overloads, which would also have to be redeclared:
       SomeObject opAdd(SomeObject x)          // BEFORE
       in SomeObject opAdd(in SomeObject x)    // AFTER
Then, similarly, you redeclare every other function that requires it, in every file, least-dependent (that is, most low-level) file first. The absolute simplest way to do this is to declare EVERYTHING that isn't "out" or "inout" as "in", and then remove the "in" keyword from things that won't compile. Providing you attack the source code in the right order, this should convert everything that needs it. And it might even show up a few bugs that got missed along the way! CONCLUSION This is achieveable, and simple. The remaining questions are: (i) does the D community want this, given that recoding of a lot of source files will be necessary, and (ii) how does Walter feel about implementing it, given that the illegalities would be relatively easy to spot? OH YEAH - I FORGOT Given the declaration:
       f(out SomeType x)
I would *assume* (though I haven't done the experiment) that either the caller or the callee overwrites any former value of x with the init value of SomeType, before doing anything with x. If it doesn't, it should. Arcane Jill
-- I will not be using Plan 9 in the creation of weapons of mass destruction to be used by nations other than the US.
May 27 2004
prev sibling next sibling parent "Kris" <someidiot earthlink.dot.dot.dot.net> writes:
I'd like to attempt a further clarification regarding the three types of
'const' usage identified:

1) compile-time constants, such as enum. You cannot take the address of
these.

2) final instances of arrays, structs, literals, objects, whatever. The
memory occupied by these should likely be initialized at the point of
declaration, and cannot be changed once set. It is conceivable (and often
necessary) to take the address of these, and it might be useful to think of
them as ROM candidates. Note that final objects might be a bit tricky.

3) DbC constraints that prevent an argument from being internally modified,
either wholly or in specific ways. In addition, there's the issue of
pass-by-value/pass-by-reference for structs that muddies the keyword water
somewhat within this arena.

- Kris


"Arcane Jill" <Arcane_member pathlink.com> wrote in message
news:c94adp$20fe$1 digitaldaemon.com...
 I have given a lot of thought to the whole "const" thing, and it seems to
me
 that there is a way around this that will keep everybody happy. (It was
not I
 who thought of this, by the way - I'm just clarifying).

 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function
will
 not modify something

 The keyword "const" is implemented in D to allow use (1) only.

 It has been suggested that the distinction between (1) and (2) could be
decided
 by the compiler. Alas, that may not be so. Given the declaration:

       const int FORTY_TWO = 42;
the expression (&FORTY_TWO) should be illegal in case (1), but legal in
case
 (2). Therefore, if we wish the language to support option (2), we need a
need a
 new keyword. I suggest re-using "final". Thus:

       const int FORTY_TWO = 42;       // Not stored anywhere, therefore
cannot take address
       final int FORTY_TWO = 42;       // Stored in ROM, so address can
be taken, but value may not be changed
 This brings us to the can of worms that is definition (3). I believe we
can do
 this. I believe it can work. Here's how:

 The CURRENT situation is that function parameters may be declared in three
 different ways. These are:

 (i)   in (the default)
 (ii)  out
 (iii) inout

 To implement DbC-readonly assertions, we actually need FOUR states. For
 consistency, these should be:

 (i)   (default)
 (ii)  in
 (iii) out
 (iv)  inout

 (default) is what you get if you specify none of the other keywords. For
 DbC-readonly to work, we only need to state that out, inout, and default,
shall
 work exactly as before (current behavior is unchanged), but new behavior
needs
 to be defined for anything declared using "in". I shall describe this new
 behavior below.

 Note though that in addition to its placement before formal function
paramters,
 "in" must also be placeable before member function declarations, like
this:
       class A
       {
           in void f()         // a contract that the function will not
modify any of A's member variables
           {
               // do stuff
           }
       }
For reference, the C++ syntax for doing this is:
       class A
       {
           void f() const      // a contract that the function will not
modify any of A's member variables
           {
               // do stuff
           }
       };
Above, I talked about a difference in behavior. The important question is
HOW
 this is going to work? Specifically, what gets passed where? How can the
 compiler spot a contract violation? These questions I shall answer, now.

 To the first point, parameters are passed exactly as they are now. Current
 behavior is suffient.

 To the second point, given the declaration:

       void f(in SomeObject a)
the following would all be compile-time errors if they occur within the
body of
 the function:

 (1)     a = expression;     // illegal always
 (2)     a.m = expression;   // illegal always
 (3)     a.g();              // illegal unless g() was declared as in void
g()
 (4)     g(a)                // illegal unless g() was declared as void
g(in
 SomeObject a)

 These tests are sufficient to catch all contract violations at
compile-time.
 (The same tests could also be used to verify that "final" objects are not
 modified).

 Incidently, the statement:

       this = expression;
should /always/ be illegal, regardless of whether or not the function was declared with "in". Note that currently, that statement is allowed, The consequences of executing it are undefined. CONSEQUENCES If we were starting from scratch, there would be no consequences. But we
are not
 starting from scratch. We already have a large body of code out there,
much of
 which will not immediately compile under this proposed change.

 There is an immediate consequence for getter functions (properties). All
 property getter functions would have to be redclared, like this:

       int x() { return x_; }      // BEFORE
       in int x() { return x_; }   // AFTER
Similarly, there is an immediate consequence for the non-assigning
operator
 overloads, which would also have to be redeclared:

       SomeObject opAdd(SomeObject x)          // BEFORE
       in SomeObject opAdd(in SomeObject x)    // AFTER
Then, similarly, you redeclare every other function that requires it, in
every
 file, least-dependent (that is, most low-level) file first. The absolute
 simplest way to do this is to declare EVERYTHING that isn't "out" or
"inout" as
 "in", and then remove the "in" keyword from things that won't compile.
Providing
 you attack the source code in the right order, this should convert
everything
 that needs it. And it might even show up a few bugs that got missed along
the
 way!

 CONCLUSION

 This is achieveable, and simple. The remaining questions are: (i) does the
D
 community want this, given that recoding of a lot of source files will be
 necessary, and (ii) how does Walter feel about implementing it, given that
the
 illegalities would be relatively easy to spot?

 OH YEAH - I FORGOT

 Given the declaration:

       f(out SomeType x)
I would *assume* (though I haven't done the experiment) that either the
caller
 or the callee overwrites any former value of x with the init value of
SomeType,
 before doing anything with x. If it doesn't, it should.


 Arcane Jill
May 27 2004
prev sibling next sibling parent Derek Parnell <derek psych.ward> writes:
On Thu, 27 May 2004 08:55:21 +0000 (UTC), Arcane Jill wrote:

 I have given a lot of thought to the whole "const" thing, and it seems to me
 that there is a way around this that will keep everybody happy. (It was not I
 who thought of this, by the way - I'm just clarifying).
 
 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function will
 not modify something
 
 The keyword "const" is implemented in D to allow use (1) only.
So this idea is a LITERAL value that does not take up any RAM at runtime, right? If so, maybe the keyword 'literal' could be used instead of 'const'. literal FORTY_TWO = 42; Note that the data type information is redundant as it is always the same datatype as the RHS expression. literal PI = 3.1415926535897932384626433832795; Of course if we had array and structure literals this would be nice too. struct Point{ int X,Y;}; literal STARTPOINT = Point(X=5,Y=10); but let's not get ahead of ourselves ;-) -- Derek 28/May/04 10:06:44 AM
May 27 2004
prev sibling parent "Matthew" <matthew.hat stlsoft.dot.org> writes:
Interesting. I look forward to hearing what Walter thinks of this, but I'm going
to keep breathing while I wait. <G>

"Arcane Jill" <Arcane_member pathlink.com> wrote in message
news:c94adp$20fe$1 digitaldaemon.com...
 I have given a lot of thought to the whole "const" thing, and it seems to me
 that there is a way around this that will keep everybody happy. (It was not I
 who thought of this, by the way - I'm just clarifying).

 Summary - the story so far:
 Three uses of const have been identified in C++
 (1) To declare a complile-time constant
 (2) To declare an instance of something which needs to be stored in ROM
 (3) As part of Design-By-Contract, to specify a contract that a function will
 not modify something

 The keyword "const" is implemented in D to allow use (1) only.

 It has been suggested that the distinction between (1) and (2) could be decided
 by the compiler. Alas, that may not be so. Given the declaration:

       const int FORTY_TWO = 42;
the expression (&FORTY_TWO) should be illegal in case (1), but legal in case (2). Therefore, if we wish the language to support option (2), we need a need a new keyword. I suggest re-using "final". Thus:
       const int FORTY_TWO = 42;       // Not stored anywhere, therefore
cannot take address
       final int FORTY_TWO = 42;       // Stored in ROM, so address can be
taken, but value may not be changed
 This brings us to the can of worms that is definition (3). I believe we can do
 this. I believe it can work. Here's how:

 The CURRENT situation is that function parameters may be declared in three
 different ways. These are:

 (i)   in (the default)
 (ii)  out
 (iii) inout

 To implement DbC-readonly assertions, we actually need FOUR states. For
 consistency, these should be:

 (i)   (default)
 (ii)  in
 (iii) out
 (iv)  inout

 (default) is what you get if you specify none of the other keywords. For
 DbC-readonly to work, we only need to state that out, inout, and default, shall
 work exactly as before (current behavior is unchanged), but new behavior needs
 to be defined for anything declared using "in". I shall describe this new
 behavior below.

 Note though that in addition to its placement before formal function paramters,
 "in" must also be placeable before member function declarations, like this:

       class A
       {
           in void f()         // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       }
For reference, the C++ syntax for doing this is:
       class A
       {
           void f() const      // a contract that the function will not modify
any of A's member variables
           {
               // do stuff
           }
       };
Above, I talked about a difference in behavior. The important question is HOW this is going to work? Specifically, what gets passed where? How can the compiler spot a contract violation? These questions I shall answer, now. To the first point, parameters are passed exactly as they are now. Current behavior is suffient. To the second point, given the declaration:
       void f(in SomeObject a)
the following would all be compile-time errors if they occur within the body of the function: (1) a = expression; // illegal always (2) a.m = expression; // illegal always (3) a.g(); // illegal unless g() was declared as in void g() (4) g(a) // illegal unless g() was declared as void g(in SomeObject a) These tests are sufficient to catch all contract violations at compile-time. (The same tests could also be used to verify that "final" objects are not modified). Incidently, the statement:
       this = expression;
should /always/ be illegal, regardless of whether or not the function was declared with "in". Note that currently, that statement is allowed, The consequences of executing it are undefined. CONSEQUENCES If we were starting from scratch, there would be no consequences. But we are
not
 starting from scratch. We already have a large body of code out there, much of
 which will not immediately compile under this proposed change.

 There is an immediate consequence for getter functions (properties). All
 property getter functions would have to be redclared, like this:

       int x() { return x_; }      // BEFORE
       in int x() { return x_; }   // AFTER
Similarly, there is an immediate consequence for the non-assigning operator overloads, which would also have to be redeclared:
       SomeObject opAdd(SomeObject x)          // BEFORE
       in SomeObject opAdd(in SomeObject x)    // AFTER
Then, similarly, you redeclare every other function that requires it, in every file, least-dependent (that is, most low-level) file first. The absolute simplest way to do this is to declare EVERYTHING that isn't "out" or "inout" as "in", and then remove the "in" keyword from things that won't compile.
Providing
 you attack the source code in the right order, this should convert everything
 that needs it. And it might even show up a few bugs that got missed along the
 way!

 CONCLUSION

 This is achieveable, and simple. The remaining questions are: (i) does the D
 community want this, given that recoding of a lot of source files will be
 necessary, and (ii) how does Walter feel about implementing it, given that the
 illegalities would be relatively easy to spot?

 OH YEAH - I FORGOT

 Given the declaration:

       f(out SomeType x)
I would *assume* (though I haven't done the experiment) that either the caller or the callee overwrites any former value of x with the init value of SomeType, before doing anything with x. If it doesn't, it should. Arcane Jill
Jun 04 2004