digitalmars.D.learn - What's wrong with just a runtime-checked const?
- Reiner Pope (69/72) Jul 15 2006 Disclaimer: my background is a small amount of Java and C#, so perhaps I...
- Andrei Khropov (14/31) Jul 15 2006 I think things that can be checked at compile-time should be checked at
- xs0 (13/27) Jul 16 2006 Well, I don't think you completely missed the point, but doing it would
- Reiner Pope (69/98) Jul 16 2006 Yeah, I've thought about it a fair bit more and compatibility seems to
- xs0 (24/91) Jul 17 2006 Agreed :) It's definitely more flexible, but the question is whether the...
- Reiner Pope (43/154) Jul 18 2006 If you stored it separately, it wouldn't be accessible easily to the
- xs0 (22/59) Jul 18 2006 Well, I don't know much about function calling either (well, you put
- Reiner Pope (9/20) Jul 18 2006 I also meant DbC preconditions and postconditions, which I believe are
- Bruno Medeiros (11/63) Jul 18 2006 I'm not following you. That code example shows how a static const
- Reiner Pope (8/36) Jul 18 2006 You seem to have ignored my more recent example. The problem is that
- Bruno Medeiros (9/47) Jul 20 2006 Are you saying that in that function, what is returned sometimes needs
- Don Clugston (10/55) Jul 20 2006 Yes.
- Bruno Medeiros (7/70) Jul 21 2006 Ok, I'm not following you guys. Can you explain from scratch what it is
- xs0 (52/55) Jul 21 2006 It's really simple. Say you have a function that sometimes modifies its
- Bruno Medeiros (19/89) Jul 26 2006 Ah, I understand the objective now.
- xs0 (14/30) Jul 27 2006 Well, it is a contract (though shall not modify a readonly array), just
- Reiner Pope (8/22) Jul 18 2006 This is (to me) the biggest issue to be resolved. I think that the best
am completely confusing the issue because of practically no C++ experience. Please tell me if that's the case. Walter seems to have two main objections to a C++-style const: 1. it can be subverted with a const_cast, removing any guarantees about it. 2. it is hard to implement, but this doesn't seem to concern him as much as the former. People have expressed a desire for const especially to ensure the Copy-on-Write idiom, and to make libraries more robust. Wouldn't a runtime const check be much more flexible than a compile-time check? Const-safeness is fundamentally a correctness-checking feature, just like unit tests, so why not make it operate exactly like unit tests? I'm thinking of something like array bounds checking:If an index is out of bounds, an ArrayBoundsError exception is raised if detected at runtime, and an error if detected at compile time. A program may not rely on array bounds checking happeningand also:Implementation Note: Compilers should attempt to detect array bounds errors at compile time, for example:...Insertion of array bounds checking code at runtime should be turned on and off with a compile time switch.This would work in const this way: everything with pointer semantics (classes, arrays, actual pointers) has a hidden (*) field called isConst. This field is stored with the pointer, not the data, so it is always passed by value, just like arrays. The compiler inserts the following code into the precondition of every function that could cause a mutation: assert(!isConst, "Mutation of a const variable detected"); /* Of course, since isConst is hidden, only the compiler has access to it, but you get the idea */ This then behaves like a unit-test, so it is removed for a release version, avoiding the slow-down and extra memory required. And since it is a field, not a type modifier, you don't need to go through your code and insert a whole lot of const's everywhere, because the compiler doesn't need to PROVE it is const-correct -- that's what unit-testing helps with. Let's give some examples: class Foo { private char[] bar; const char[] getCoWBar() // The const tells the compiler to set the isConst field in the pointer to true { return bar; } char[] getNonCoWBar() { return bar; } } ... Foo foo; char[] a = foo.getCoWBar(); // OK, even though getCoWBar is const and a isn't a[0] = 'a'; // Error -- compile-time if the compiler is smart enough, otherwise runtime a = a.dup; // OK a[0] = 'a'; // OK, because we duplicate it. a = getNonCoWBar(); // OK a[0] = 'a'; // OK -- it isn't const In this, the only way to get a non-const pointer from a const pointer is by duplicating it. This makes data that can never change as simple as never having a non-const pointer to it: const char[] foo = "bar"; // OK char[] a = foo; // OK a[0] = 'a'; // Error The even better thing about this is that most code doesn't need to have const-correctness in mind when writing it, and it shouldn't break existing code, because the only code that will break is code that is buggy code anyway. Am I completely missing the point? Will it cause memory/speed issues (keeping in mind that it's only for debug builds)? * The field is hidden because it can't be safely used by the code as it is left out in a release build.
Jul 15 2006
Reiner Pope wrote:completely confusing the issue because of practically no C++ experience. Please tell me if that's the case. Walter seems to have two main objections to a C++-style const: 1. it can be subverted with a const_cast, removing any guarantees about it.Different C++ style casts are not supported in D so it's not an issue, I think.2. it is hard to implement, but this doesn't seem to concern him as much as the former. People have expressed a desire for const especially to ensure the Copy-on-Write idiom, and to make libraries more robust. Wouldn't a runtime const check be much more flexible than a compile-time check? Const-safeness is fundamentally a correctness-checking feature, just like unit tests, so why not make it operate exactly like unit tests?I think things that can be checked at compile-time should be checked at compile-time and don't slow down runtime code. Your proposed type of checks also has a kind of dynamic flavor (Smalltalk/Python like) which is not very D way :-) Unit tests are different. They are rather "post-compile" or "pre-runtime" in fact. I suppose you actually mean DbC, right?The even better thing about this is that most code doesn't need to have const-correctness in mind when writing itI think that const-correct code is important and expresses good design and also aids compiler in optimization. ------------------------------------------------------------------------------- Anyway, I think const discussion should be postponed until we get all the things with imports right which is the highest priority for now, IMHO.
Jul 15 2006
Reiner Pope wrote:Wouldn't a runtime const check be much more flexible than a compile-time check? Const-safeness is fundamentally a correctness-checking feature, just like unit tests, so why not make it operate exactly like unit tests? I'm thinking of something like array bounds checking: [snip] The even better thing about this is that most code doesn't need to have const-correctness in mind when writing it, and it shouldn't break existing code, because the only code that will break is code that is buggy code anyway. Am I completely missing the point? Will it cause memory/speed issues (keeping in mind that it's only for debug builds)?Well, I don't think you completely missed the point, but doing it would cause all sorts of issues: - where should the tag be placed? you can't put it inside the pointer, as there are no free bits; you also can't put it next to a pointer, as it would affect memory layout of structures (in particular, it would make debug-built and release-built code non-interoperable). - it can still be trivially subverted - just cast to int/long and back - you can't just check at the beginning of a function - you can get the pointer in the middle of it; you can also get the pointer in _another_ function (from a global or in a multi-threaded program); checking at every access would be too expensive, I think, even for a debug build xs0
Jul 16 2006
xs0 wrote:Reiner Pope wrote:Wouldn't a runtime const check be much more flexible than a compile-time check? Const-safeness is fundamentally a correctness-checking feature, just like unit tests, so why not make it operate exactly like unit tests? I'm thinking of something like array bounds checking: [snip] The even better thing about this is that most code doesn't need to have const-correctness in mind when writing it, and it shouldn't break existing code, because the only code that will break is code that is buggy code anyway. Am I completely missing the point? Will it cause memory/speed issues (keeping in mind that it's only for debug builds)?Well, I don't think you completely missed the point, but doing it would cause all sorts of issues:- where should the tag be placed? you can't put it inside the pointer, as there are no free bits; you also can't put it next to a pointer, as it would affect memory layout of structures (in particular, it would make debug-built and release-built code non-interoperable).Yeah, I've thought about it a fair bit more and compatibility seems to be the main issue. I did indeed mean that it would be stored next to the pointer, but it does seem like that could cause problems with interoperability. If all the info could be stored separately in a bit-array managed by, say, the GC, then perhaps that would avoid the issue of interoperability. But I haven't quite worked out how that would be implemented yet...- it can still be trivially subverted - just cast to int/long and backI presume this point leads on from the above, where you assumed that the bit was stored *in* the pointer. So that's a null issue, since it clearly isn't.- you can't just check at the beginning of a functionI don't understand your reason for this, but when I started writing down the details more specifically, I realised that the advantage of runtime const checking is a *huge* increase in flexibility, and having to say at compile-time whether a function is const or not removes that flexibility in my mind.- you can get the pointer in the middle of it; you can also get the pointer in _another_ function (from a global or in a multi-threaded program); checking at every access would be too expensive, I think, even for a debug buildI don't know how expensive it would be. Considering that all it is is assert(!isConst); that operation doesn't seem too hard. And of course, the compiler is free to optimize away duplicate versions of it, like this: _member = 4; _member = 3; Obviously there's no need to check isConst before the second assignment. And on the issue of the pointer suddenly changing in the middle of the function: well, it's actually not an issue, because I worked out that it's best for the function to have a local copy of the isConst variable, which means no other resources can change it (other than a malicious hacker with pointers). This is also conceptually right, because it doesn't make sense to give a function a certain level of modification access and then change it while it is running. However, I think now that checking at every access is the only flexible way to manage const. In my view, we either need runtime-checked const or nothing. Consider this, and tell me how we can avoid excess dup calls if we use C++-style const-checking: class foo { private char[] _name; public const char[] getName() { return _name; } ... } ... Import std.string; foo f; /*const*/ char[] name = f.getName(); char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure name_m = name_m.dup(); With runtime const checking, we could fix that, though, by also getting a bool variable back from tolower() which says whether it returned the original string or a copy. Quite simply, compile-time const won't let you do that, so you will actually produce *slower* code. If you're interested, I'm trying to come up with a draft which has the actual details, jut so it gives a more solid ground for discussion. My problem at the moment is just what we mentioned earlier: operability between debug and release builds. I suggested that each function should be passed, behind the scenes, the relevant isConst variable, but that's not compatible with release builds. I then thought that you could have two copies of a function: one for const variables and one for non-const variables. The correct one would be chosen at runtime, and external packages would always choose the one for non-const variables (which is a copy of the one for const variables, but with checking turned off). This effectively duplicates the amount of machine code generated by the compiler, but I am convinced that a solution like I propose is much better than a simple static checking scheme for the flexibility reason I mentioned above. Reiner
Jul 16 2006
Well, it's not really important where it's stored - if you cast to int/long, you lose the constness information in any case...- it can still be trivially subverted - just cast to int/long and backI presume this point leads on from the above, where you assumed that the bit was stored *in* the pointer. So that's a null issue, since it clearly isn't.Agreed :) It's definitely more flexible, but the question is whether the cost is low enough for the feature to still be usable, and whether it's realistically implementable at all.. Note that even if the GC is modified, and the constness information is stored somewhere externally, it can take a lot of updating when passing pointers/references around...- you can't just check at the beginning of a functionI don't understand your reason for this, but when I started writing down the details more specifically, I realised that the advantage of runtime const checking is a *huge* increase in flexibility, and having to say at compile-time whether a function is const or not removes that flexibility in my mind.Well, you assume the optimizer, but it's not usually used with debug builds, even if it supported this optimization. And there definitely are cases where each access would require a check; in a multi-threaded program you can hardly assume anything about non-local variables, especially that they don't change :)- you can get the pointer in the middle of it; you can also get the pointer in _another_ function (from a global or in a multi-threaded program); checking at every access would be too expensive, I think, even for a debug buildI don't know how expensive it would be. Considering that all it is is assert(!isConst); that operation doesn't seem too hard. And of course, the compiler is free to optimize away duplicate versions of it, like this: _member = 4; _member = 3;Obviously there's no need to check isConst before the second assignment. And on the issue of the pointer suddenly changing in the middle of the function: well, it's actually not an issue, because I worked out that it's best for the function to have a local copy of the isConst variable, which means no other resources can change it (other than a malicious hacker with pointers). This is also conceptually right, because it doesn't make sense to give a function a certain level of modification access and then change it while it is running.That is true only for correct programs ;) Considering how the purpose is to catch bugs, though, you can't make that assumption...In my view, we either need runtime-checked const or nothing. Consider this, and tell me how we can avoid excess dup calls if we use C++-style const-checking: class foo { private char[] _name; public const char[] getName() { return _name; } ... } ... Import std.string; foo f; /*const*/ char[] name = f.getName(); char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure name_m = name_m.dup();if (name_m is name) but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.With runtime const checking, we could fix that, though, by also getting a bool variable back from tolower() which says whether it returned the original string or a copy. Quite simply, compile-time const won't let you do that, so you will actually produce *slower* code.Well, if you wanted, you could make tolower() return that already, it doesn't have much to do with constness..If you're interested, I'm trying to come up with a draft which has the actual details, jut so it gives a more solid ground for discussion. My problem at the moment is just what we mentioned earlier: operability between debug and release builds. I suggested that each function should be passed, behind the scenes, the relevant isConst variable, but that's not compatible with release builds. I then thought that you could have two copies of a function: one for const variables and one for non-const variables. The correct one would be chosen at runtime, and external packages would always choose the one for non-const variables (which is a copy of the one for const variables, but with checking turned off). This effectively duplicates the amount of machine code generated by the compiler, but I am convinced that a solution like I propose is much better than a simple static checking scheme for the flexibility reason I mentioned above.Sure, I'm interested :) But the way I see it, it's a practically impossible problem to solve in a way that is time/space efficient, useful, and easy to use, even more so if the solution is not static.. xs0
Jul 17 2006
xs0 wrote:If you stored it separately, it wouldn't be accessible easily to the user. This is obviously an implementation detail, and not the interesting part of the discussion, though. :)Well, it's not really important where it's stored - if you cast to int/long, you lose the constness information in any case...- it can still be trivially subverted - just cast to int/long and backI presume this point leads on from the above, where you assumed that the bit was stored *in* the pointer. So that's a null issue, since it clearly isn't.Yes, and I don't know enough about function calling to know what exactly is possible, etc.Agreed :) It's definitely more flexible, but the question is whether the cost is low enough for the feature to still be usable, and whether it's realistically implementable at all.. Note that even if the GC is modified, and the constness information is stored somewhere externally, it can take a lot of updating when passing pointers/references around...- you can't just check at the beginning of a functionI don't understand your reason for this, but when I started writing down the details more specifically, I realised that the advantage of runtime const checking is a *huge* increase in flexibility, and having to say at compile-time whether a function is const or not removes that flexibility in my mind.I don't see the problem: you pass isConst by value, so there is no way for anything external to alter the const-ness level that that particular function has. How can that cause any problems?Well, you assume the optimizer, but it's not usually used with debug builds, even if it supported this optimization. And there definitely are cases where each access would require a check; in a multi-threaded program you can hardly assume anything about non-local variables, especially that they don't change :)- you can get the pointer in the middle of it; you can also get the pointer in _another_ function (from a global or in a multi-threaded program); checking at every access would be too expensive, I think, even for a debug buildI don't know how expensive it would be. Considering that all it is is assert(!isConst); that operation doesn't seem too hard. And of course, the compiler is free to optimize away duplicate versions of it, like this: _member = 4; _member = 3;Since the programmer shouldn't be able to access the isConst variable, there's no way to change it, so it shouldn't be a problem.Obviously there's no need to check isConst before the second assignment. And on the issue of the pointer suddenly changing in the middle of the function: well, it's actually not an issue, because I worked out that it's best for the function to have a local copy of the isConst variable, which means no other resources can change it (other than a malicious hacker with pointers). This is also conceptually right, because it doesn't make sense to give a function a certain level of modification access and then change it while it is running.That is true only for correct programs ;) Considering how the purpose is to catch bugs, though, you can't make that assumption...Yes, but try getting that to work with a statically-checked const. It simply won't work unless you have a very smart checker. Of course, you could leave const out altogether, but then we would be making no progress. Let me demonstrate the static const-checking problem: char[] tolower(const char[] input) // the input must be const, because we agree with CoW, so we won't change it { // do some stuff if ( a write is necessary ) { // copy it into another variable, since we can't change input (it's const) } return something; // This something could possibly be input, so it also needs to be declared const. So we go back and make the return value of the function also a const. } // Now, since the return value is const, we *must* dup it. Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.In my view, we either need runtime-checked const or nothing. Consider this, and tell me how we can avoid excess dup calls if we use C++-style const-checking: class foo { private char[] _name; public const char[] getName() { return _name; } ... } ... Import std.string; foo f; /*const*/ char[] name = f.getName(); char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure name_m = name_m.dup();if (name_m is name)but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).I've said above how it has to do with constness. Constness is not required, but if you decide to use it statically, then you get all these problems which you can't solve.With runtime const checking, we could fix that, though, by also getting a bool variable back from tolower() which says whether it returned the original string or a copy. Quite simply, compile-time const won't let you do that, so you will actually produce *slower* code.Well, if you wanted, you could make tolower() return that already, it doesn't have much to do with constness..How important is time/space in a debug/unittest build? Walter said somewhere that unit tests often cause slow-downs to half speeds, so is speed really such an issue then? Similarly, unit tests builds are 3x the size of release builds (on my current test, anyway). Should we really be worried about efficiency concerns in non-release builds given the losses in efficiency we already accept?If you're interested, I'm trying to come up with a draft which has the actual details, jut so it gives a more solid ground for discussion. My problem at the moment is just what we mentioned earlier: operability between debug and release builds. I suggested that each function should be passed, behind the scenes, the relevant isConst variable, but that's not compatible with release builds. I then thought that you could have two copies of a function: one for const variables and one for non-const variables. The correct one would be chosen at runtime, and external packages would always choose the one for non-const variables (which is a copy of the one for const variables, but with checking turned off). This effectively duplicates the amount of machine code generated by the compiler, but I am convinced that a solution like I propose is much better than a simple static checking scheme for the flexibility reason I mentioned above.Sure, I'm interested :) But the way I see it, it's a practically impossible problem to solve in a way that is time/space efficient, useful, and easy to use, even more so if the solution is not static..xs0
Jul 18 2006
Well, I don't know much about function calling either (well, you put some stuff on stack and some on registers, and call the function :). But regardless of how it's done, you need to propagate constness information whenever you - assign a variable a new value - copy the variable (this includes passing parameters to functions and getting return values back)Agreed :) It's definitely more flexible, but the question is whether the cost is low enough for the feature to still be usable, and whether it's realistically implementable at all.. Note that even if the GC is modified, and the constness information is stored somewhere externally, it can take a lot of updating when passing pointers/references around...Yes, and I don't know enough about function calling to know what exactly is possible, etc.Hmm, since when was constness a property of a function? It's the data that is const or not, and one way or another, you need to know the constness for each pointer/reference separately.And there definitely are cases where each access would require a check; in a multi-threaded program you can hardly assume anything about non-local variables, especially that they don't change :)I don't see the problem: you pass isConst by value, so there is no way for anything external to alter the const-ness level that that particular function has. How can that cause any problems?Hmm, now I really think you'd like to achieve constness on a per-function-invocation basis.. Are you sure that is what you want and that it is useful? I think it would be a good time you restated your proposal :)Since the programmer shouldn't be able to access the isConst variable, there's no way to change it, so it shouldn't be a problem.Obviously there's no need to check isConst before the second assignment. And on the issue of the pointer suddenly changing in the middle of the function: well, it's actually not an issue, because I worked out that it's best for the function to have a local copy of the isConst variable, which means no other resources can change it (other than a malicious hacker with pointers). This is also conceptually right, because it doesn't make sense to give a function a certain level of modification access and then change it while it is running.That is true only for correct programs ;) Considering how the purpose is to catch bugs, though, you can't make that assumption...How important is time/space in a debug/unittest build? Walter said somewhere that unit tests often cause slow-downs to half speeds, so is speed really such an issue then? Similarly, unit tests builds are 3x the size of release builds (on my current test, anyway). Should we really be worried about efficiency concerns in non-release builds given the losses in efficiency we already accept?Hmm, afaik, unit tests are only run once per execution and don't otherwise affect the speed of execution. Their size also depends on the size of the code, but keeping runtime constness information would have costs depending on the size of _data_, a much bigger problem I think... And sure, you need to worry about efficiency. Something like a 10% speed loss is probably acceptable, but something that will slow your program by a factor of 20 is often not acceptable, even in debug builds... xs0
Jul 18 2006
Hmm, afaik, unit tests are only run once per execution and don't otherwise affect the speed of execution. Their size also depends on the size of the code, but keeping runtime constness information would have costs depending on the size of _data_, a much bigger problem I think...I also meant DbC preconditions and postconditions, which I believe are only compiled in during debug mode. These are run at every function invocation, which can mean a big slow-down (especially with long invariants).And sure, you need to worry about efficiency. Something like a 10% speed loss is probably acceptable, but something that will slow your program by a factor of 20 is often not acceptable, even in debug builds...Since no implementation has been done, we can only wildly speculate about slow-downs. Since it's all getting very confused, I'll work out the details gradually and see if I come across any more problems, and then post my results here. Reinerxs0
Jul 18 2006
Reiner Pope wrote:I'm not following you. That code example shows how a static const checking should work, so what is the problem with that? And in: char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure You ask if we can modify name_m, the answer is yes if the return type of tolower is non-const, no if it const, so what's the issue here? -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DYes, but try getting that to work with a statically-checked const. It simply won't work unless you have a very smart checker. Of course, you could leave const out altogether, but then we would be making no progress. Let me demonstrate the static const-checking problem: char[] tolower(const char[] input) // the input must be const, because we agree with CoW, so we won't change it { // do some stuff if ( a write is necessary ) { // copy it into another variable, since we can't change input (it's const) } return something; // This something could possibly be input, so it also needs to be declared const. So we go back and make the return value of the function also a const. } // Now, since the return value is const, we *must* dup it. Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.In my view, we either need runtime-checked const or nothing. Consider this, and tell me how we can avoid excess dup calls if we use C++-style const-checking: class foo { private char[] _name; public const char[] getName() { return _name; } ... } ... Import std.string; foo f; /*const*/ char[] name = f.getName(); char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure name_m = name_m.dup();if (name_m is name)but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).
Jul 18 2006
Bruno Medeiros wrote:You seem to have ignored my more recent example. The problem is that static checking *forces* the return value to be const, which in turn forces you to dup it. You don't always need to do that:Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.I'm not following you. That code example shows how a static const checking should work, so what is the problem with that? And in: char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure You ask if we can modify name_m, the answer is yes if the return type of tolower is non-const, no if it const, so what's the issue here?but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).because we agree with CoW, so we won't change it */char[] tolower(const char[] input) /* the input must be const,it's const)*/{ // do some stuff if ( a write is necessary ) { /* copy it into another variable, since we can't change inputalso needs to be declared const. So we go back and make the return value of the function also a const. */} return something; /* This something could possibly be input, so it} // Now, since the return value is const, we *must* dup it.
Jul 18 2006
Reiner Pope wrote:Bruno Medeiros wrote:Are you saying that in that function, what is returned sometimes needs do be const(readonly) and sometimes not? And because of that the function needs to return const everytime? But then why not have the function return non-const and do the duping when necessary? -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DYou seem to have ignored my more recent example. The problem is that static checking *forces* the return value to be const, which in turn forces you to dup it. You don't always need to do that: >> char[] tolower(const char[] input) /* the input must be const, because we agree with CoW, so we won't change it */ >> { >> // do some stuff >> if ( a write is necessary ) >> { /* copy it into another variable, since we can't change input it's const)*/ >> } >> return something; /* This something could possibly be input, so it also needs to be declared const. So we go back and make the return value of the function also a const. */ >> } >> >> // Now, since the return value is const, we *must* dup it. >>Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.I'm not following you. That code example shows how a static const checking should work, so what is the problem with that? And in: char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure You ask if we can modify name_m, the answer is yes if the return type of tolower is non-const, no if it const, so what's the issue here?but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).
Jul 20 2006
Bruno Medeiros wrote:Reiner Pope wrote:Yes. And because of that theBruno Medeiros wrote:Are you saying that in that function, what is returned sometimes needs do be const(readonly) and sometimes not?You seem to have ignored my more recent example. The problem is that static checking *forces* the return value to be const, which in turn forces you to dup it. You don't always need to do that: >> char[] tolower(const char[] input) /* the input must be const, because we agree with CoW, so we won't change it */ >> { >> // do some stuff >> if ( a write is necessary ) >> { /* copy it into another variable, since we can't change input it's const)*/ >> } >> return something; /* This something could possibly be input, so it also needs to be declared const. So we go back and make the return value of the function also a const. */ >> } >> >> // Now, since the return value is const, we *must* dup it. >>Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.I'm not following you. That code example shows how a static const checking should work, so what is the problem with that? And in: char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure You ask if we can modify name_m, the answer is yes if the return type of tolower is non-const, no if it const, so what's the issue here?but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).function needs to return const everytime? But then why not have the function return non-const and do the duping when necessary?Doesn't help. Either, the function has to dup every time (even if the input was a const, and it's passing it on unmodified) -- so that a user of the function can write without copying first OR the user of the function has to dup every time (even if it was already duped) In both cases, you have an unnecessary dup.
Jul 20 2006
Don Clugston wrote:Bruno Medeiros wrote:Ok, I'm not following you guys. Can you explain from scratch what it is you were trying to achieve (the purpose of the function?), which when done with static const checking causes unnecessary dups? -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DReiner Pope wrote:Yes. And because of that theBruno Medeiros wrote:Are you saying that in that function, what is returned sometimes needs do be const(readonly) and sometimes not?You seem to have ignored my more recent example. The problem is that static checking *forces* the return value to be const, which in turn forces you to dup it. You don't always need to do that: >> char[] tolower(const char[] input) /* the input must be const, because we agree with CoW, so we won't change it */ >> { >> // do some stuff >> if ( a write is necessary ) >> { /* copy it into another variable, since we can't change input it's const)*/ >> } >> return something; /* This something could possibly be input, so it also needs to be declared const. So we go back and make the return value of the function also a const. */ >> } >> >> // Now, since the return value is const, we *must* dup it. >>Can you suggest another solution other than avoiding const-checking entirely? By the way, a dedicated string class is actually an implementation of runtime const checking.I'm not following you. That code example shows how a static const checking should work, so what is the problem with that? And in: char[] name_m = tolower(name); // Can we modify name_m? Better dup it, just to make sure You ask if we can modify name_m, the answer is yes if the return type of tolower is non-const, no if it const, so what's the issue here?but, it's certainly not a pretty solution... and there's a related question - how can tolower know whether to .dup or not.. there are certainly cases where it isn't necessary.This is just another issue that static checking can't solve but runtime checking can (providing we have a modification of the libraries to support a CoW and a in-place version).function needs to return const everytime? But then why not have the function return non-const and do the duping when necessary?Doesn't help. Either, the function has to dup every time (even if the input was a const, and it's passing it on unmodified) -- so that a user of the function can write without copying first OR the user of the function has to dup every time (even if it was already duped) In both cases, you have an unnecessary dup.
Jul 21 2006
Ok, I'm not following you guys. Can you explain from scratch what it is you were trying to achieve (the purpose of the function?), which when done with static const checking causes unnecessary dups?It's really simple. Say you have a function that sometimes modifies its data and sometimes doesn't. The non-const version is simple, as it's allowed in-place modification: byte[] filter(byte[] data) { if (...) data[0] = 10; return data; } Now, whenever you have const data, you can only use it by duping: const byte[] data = foo(); byte[] filtered = filter(data.dup); That's bad, because you always have to .dup the data, even if filter() does nothing with it. So, you also write a const version: const byte[] filter(const byte[] data) { if (...) { byte[] result = data.dup; result[0] = 10; return result; } return data; } That's much better, because a copy is only made when needed. However, there's still a problem - the return type must be const, because you need to be able to return the original (const) parameter. Therefore, the information about whether a copy is made is lost. With a single function that's not even a problem, but say your filtering is configurable and you call 20 filters. If you use the const versions, 20 copies will be made, even though only one is necessary. If you use the non-const versions, you're forced to dup before the first filter, even when not necessary at all. A similar problem occurs in this case: const char[] handleDesc() { if (_handle) { } else { return "No handle"; } } Because you sometimes return a literal, you must declare the result const, even though you'll return a fresh writable char[] most of the time, which may or may not be significant. Alternatively, you can .dup the literal before returning it, but it will usually be a wasted .dup.. In both cases, the problem is the same - there is no way for the function to return information about whether a copy was made along with the data itself. It can't even neatly be solved by wrapping the thing in a struct or checking equality of references, because the type in either case is static and one way or another, you'll have to work around that (by casting or whatever) xs0
Jul 21 2006
xs0 wrote:Ah, I understand the objective now. But then, this problem you are trying to solve here is one of performance only, related to ownership management, but not necessarily the same. In particular: * Whatever mechanism is made to deal with that problem, cannot be disabled in release builds: it is not a contract, unlike const/ownership checking which is a contract, and being so can be checked at compile time, or at runtime in debug releases only. * For that same reasons, such mechanism should not be a substitute for const/ownership checking, since this latter one can be processed at compile time, which is naturally more efficient, and the former one can't. * Such mechanism is likely impractical or too hard to implement in the general sense (that is, for any data type). So perhaps the mechanism should be implement not by the language/compiler, but by the coder with existing language constructs (mixins, etc.)? -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#DOk, I'm not following you guys. Can you explain from scratch what it is you were trying to achieve (the purpose of the function?), which when done with static const checking causes unnecessary dups?It's really simple. Say you have a function that sometimes modifies its data and sometimes doesn't. The non-const version is simple, as it's allowed in-place modification: byte[] filter(byte[] data) { if (...) data[0] = 10; return data; } Now, whenever you have const data, you can only use it by duping: const byte[] data = foo(); byte[] filtered = filter(data.dup); That's bad, because you always have to .dup the data, even if filter() does nothing with it. So, you also write a const version: const byte[] filter(const byte[] data) { if (...) { byte[] result = data.dup; result[0] = 10; return result; } return data; } That's much better, because a copy is only made when needed. However, there's still a problem - the return type must be const, because you need to be able to return the original (const) parameter. Therefore, the information about whether a copy is made is lost. With a single function that's not even a problem, but say your filtering is configurable and you call 20 filters. If you use the const versions, 20 copies will be made, even though only one is necessary. If you use the non-const versions, you're forced to dup before the first filter, even when not necessary at all. A similar problem occurs in this case: const char[] handleDesc() { if (_handle) { } else { return "No handle"; } } Because you sometimes return a literal, you must declare the result const, even though you'll return a fresh writable char[] most of the time, which may or may not be significant. Alternatively, you can .dup the literal before returning it, but it will usually be a wasted .dup.. In both cases, the problem is the same - there is no way for the function to return information about whether a copy was made along with the data itself. It can't even neatly be solved by wrapping the thing in a struct or checking equality of references, because the type in either case is static and one way or another, you'll have to work around that (by casting or whatever) xs0
Jul 26 2006
Ah, I understand the objective now. But then, this problem you are trying to solve here is one of performance only, related to ownership management, but not necessarily the same. In particular:* Whatever mechanism is made to deal with that problem, cannot be disabled in release builds: it is not a contract, unlike const/ownership checking which is a contract, and being so can be checked at compile time, or at runtime in debug releases only.Well, it is a contract (though shall not modify a readonly array), just not verifiable at compile-time. Similar to in{} and out{} blocks, I guess.. It has other (potentially significant) benefits, though..* For that same reasons, such mechanism should not be a substitute for const/ownership checking, since this latter one can be processed at compile time, which is naturally more efficient, and the former one can't.I disagree about efficiency - it turns out there is negligible speed impact even if .readonly is not used at all, while it can be used for significant speed improvements with simpler code (in particular, writing a fast and safe COW function is easier). I posted test results and code in digitalmars.D, if you're interested (I think :)* Such mechanism is likely impractical or too hard to implement in the general sense (that is, for any data type). So perhaps the mechanism should be implement not by the language/compiler, but by the coder with existing language constructs (mixins, etc.)?Well, you could code your own array references (like I did for my test), but they lose on ease of use and are less flexible in general (because you can't code an inheritance tree which would mimic the built-in arrays'). On the other hand, if it was language-supported, nothing would be lost, and something would be gained.. xs0
Jul 27 2006
xs0 wrote:Well, I don't think you completely missed the point, but doing it would cause all sorts of issues: - where should the tag be placed? you can't put it inside the pointer, as there are no free bits; you also can't put it next to a pointer, as it would affect memory layout of structures (in particular, it would make debug-built and release-built code non-interoperable).This is (to me) the biggest issue to be resolved. I think that the best implementation would involve modifying the signature of functions to accept extra variables, signalling isConst. I know, this damages release/debug interoperability, so can you tell me which functions that would cause problems for? All that I can think of is (a) exported functions in libraries, and (b) functions that inline assembler calls. Am I missing any?- it can still be trivially subverted - just cast to int/long and back - you can't just check at the beginning of a function - you can get the pointer in the middle of it; you can also get the pointer in _another_ function (from a global or in a multi-threaded program); checking at every access would be too expensive, I think, even for a debug build xs0
Jul 18 2006