www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Invariants are useless the way they are defined

reply "deadalnix" <deadalnix gmail.com> writes:
As they are defined now, invariant are plain useless. I find 
myself disabling them one by one in my code as soon as cases get 
outside simple trivia.

The problem is that invariant are checked at the beginning/end on 
public function calls. As a consequence, it is impossible to use 
any public method in an invariant.

For instance, just right now I did refactor a struct to use 
bitfields, in order to make it more compact. Now I have to remove 
the invariant as field access are now function calls.

Another typical situation is when you want to assert certain 
properties in a class hierarchy, where calling virtual method is 
part of the invariant check.

invariant should (and must to be useful) be inserted at callee 
point, when the callee isn't itself subject to invariant 
insertion, and around public memeber manipulation (when the 
manipulator isn't subject to invariant insertion).

Any other pattern it doomed to create infinite recursion for non 
trivial invariant checks.
Aug 25 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
deadalnix:
 As they are defined now, invariant are plain useless.

Don't forget to add this issue to Bugzilla. So we will keep memory of it. Bye, bearophile
Aug 25 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Sunday, 25 August 2013 at 13:44:49 UTC, bearophile wrote:
 deadalnix:
 As they are defined now, invariant are plain useless.

Don't forget to add this issue to Bugzilla. So we will keep memory of it. Bye, bearophile

http://d.puremagic.com/issues/show_bug.cgi?id=10889 Done but I'm not sure this is a bug. This work as designed, it is simply that the design is improvable :D
Aug 25 2013
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/25/13 5:16 AM, deadalnix wrote:
 As they are defined now, invariant are plain useless. I find myself
 disabling them one by one in my code as soon as cases get outside simple
 trivia.

 The problem is that invariant are checked at the beginning/end on public
 function calls. As a consequence, it is impossible to use any public
 method in an invariant.

But this is what they are for - check the state of an object upon access through the client-accessible interface. Simple code factorization (use a private method forwarded to by the public one) takes care of this situation.
 For instance, just right now I did refactor a struct to use bitfields,
 in order to make it more compact. Now I have to remove the invariant as
 field access are now function calls.

I don't get this case. Code?
 Another typical situation is when you want to assert certain properties
 in a class hierarchy, where calling virtual method is part of the
 invariant check.

 invariant should (and must to be useful) be inserted at callee point,
 when the callee isn't itself subject to invariant insertion, and around
 public memeber manipulation (when the manipulator isn't subject to
 invariant insertion).

 Any other pattern it doomed to create infinite recursion for non trivial
 invariant checks.

There might be something here, but more like a performance problem. I do see how calls of public methods from an invariant could cause trouble, but I'd say that's a problem with the invariant design. Andrei
Aug 25 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Sunday, 25 August 2013 at 16:22:01 UTC, Andrei Alexandrescu 
wrote:
 On 8/25/13 5:16 AM, deadalnix wrote:
 As they are defined now, invariant are plain useless. I find 
 myself
 disabling them one by one in my code as soon as cases get 
 outside simple
 trivia.

 The problem is that invariant are checked at the beginning/end 
 on public
 function calls. As a consequence, it is impossible to use any 
 public
 method in an invariant.

But this is what they are for - check the state of an object upon access through the client-accessible interface. Simple code factorization (use a private method forwarded to by the public one) takes care of this situation.

Yes that can be worked around, but the code ends up more and more complex, with many function forwarding to one another. This can become very messy with class hierarchies.
 For instance, just right now I did refactor a struct to use 
 bitfields,
 in order to make it more compact. Now I have to remove the 
 invariant as
 field access are now function calls.

I don't get this case. Code?

struct Identifiable { union { Symbol sym; Expression expr; Type type; } import d.ast.base : TypeQualifier; import std.bitmanip; mixin(bitfields!( Tag, "tag", 2, TypeQualifier, "qual", 3, uint, "", 3, )); disable this(); // For type inference. this(typeof(null)); this(Identifiable i) { this = i; } this(Symbol s) { tag = Tag.Symbol; sym = s; } this(Expression e) { tag = Tag.Expression; expr = e; } this(QualType qt) { tag = Tag.Type; qual = qt.qualifier; type = qt.type; } } I used to have an invariant that check the sanity of the tagged union, but now I have to double the interface to the bitfield. This one is still fixable by doubling the interface, but the same thing with the bitfield in a super class from another module and you are doomed.
 Another typical situation is when you want to assert certain 
 properties
 in a class hierarchy, where calling virtual method is part of 
 the
 invariant check.

 invariant should (and must to be useful) be inserted at callee 
 point,
 when the callee isn't itself subject to invariant insertion, 
 and around
 public memeber manipulation (when the manipulator isn't 
 subject to
 invariant insertion).

 Any other pattern it doomed to create infinite recursion for 
 non trivial
 invariant checks.

There might be something here, but more like a performance problem. I do see how calls of public methods from an invariant could cause trouble, but I'd say that's a problem with the invariant design. Andrei

This is a performance issue as well. But mostly it makes invariant useless in many cases when class hierarchies are in place and probably the worst part is that invariant are not even ensured as they do not trigger when writing to public fields (as triggered by the callee, and we have no callee in this case).
Aug 25 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
deadalnix:

 Done but I'm not sure this is a bug. This work as designed, it 
 is simply that the design is improvable :D

It's not a bug, it's an enhancement, because according to you it could be a small design bug. Don't forged to add to the bug report good comments coming from Andrei or other people from this thread. Bye, bearophile
Aug 25 2013
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, August 25, 2013 14:16:28 deadalnix wrote:
 As they are defined now, invariant are plain useless. I find
 myself disabling them one by one in my code as soon as cases get
 outside simple trivia.
 
 The problem is that invariant are checked at the beginning/end on
 public function calls. As a consequence, it is impossible to use
 any public method in an invariant.
 
 For instance, just right now I did refactor a struct to use
 bitfields, in order to make it more compact. Now I have to remove
 the invariant as field access are now function calls.
 
 Another typical situation is when you want to assert certain
 properties in a class hierarchy, where calling virtual method is
 part of the invariant check.
 
 invariant should (and must to be useful) be inserted at callee
 point, when the callee isn't itself subject to invariant
 insertion, and around public memeber manipulation (when the
 manipulator isn't subject to invariant insertion).
 
 Any other pattern it doomed to create infinite recursion for non
 trivial invariant checks.

What drives me nuts is the fact that it gets checked when opAssign is called (rather than just when exiting it). That makes it impossible to have a member in an invalid state (either through an init which has an invalid state similar to how floating point values due or because the object hasn't been initialized yet) and have an invariant. At present, emplace will blow up in your face if you try and have an invariant with such types. The result is that I almost never use invariant. It ends up working better to just create a private function which is the invariant and asserting explicitly in every function where you want the check, but most of the time, that's enough of a pain that the result is that it's just not checked. Unfortunately, my enhancement request on that (not having the invariant called on entry to opAssign) was shot down, because there are some opAssigns which require that the previous state be valid rather than just replacing it (which means that what you really need is a way to declare an invariant but disable it in certain places). - Jonathan M Davis
Aug 25 2013
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/25/13 2:56 PM, Jonathan M Davis wrote:
 On Sunday, August 25, 2013 14:16:28 deadalnix wrote:
 As they are defined now, invariant are plain useless. I find
 myself disabling them one by one in my code as soon as cases get
 outside simple trivia.

 The problem is that invariant are checked at the beginning/end on
 public function calls. As a consequence, it is impossible to use
 any public method in an invariant.

 For instance, just right now I did refactor a struct to use
 bitfields, in order to make it more compact. Now I have to remove
 the invariant as field access are now function calls.

 Another typical situation is when you want to assert certain
 properties in a class hierarchy, where calling virtual method is
 part of the invariant check.

 invariant should (and must to be useful) be inserted at callee
 point, when the callee isn't itself subject to invariant
 insertion, and around public memeber manipulation (when the
 manipulator isn't subject to invariant insertion).

 Any other pattern it doomed to create infinite recursion for non
 trivial invariant checks.

What drives me nuts is the fact that it gets checked when opAssign is called (rather than just when exiting it). That makes it impossible to have a member in an invalid state (either through an init which has an invalid state similar to how floating point values due or because the object hasn't been initialized yet) and have an invariant. At present, emplace will blow up in your face if you try and have an invariant with such types.

emplace() should not call assignment for elaborate types. Andrei
Aug 25 2013
prev sibling next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 08/25/2013 05:16 AM, deadalnix wrote:

 The problem is that invariant are checked at the beginning/end on public
 function calls. As a consequence, it is impossible to use any public
 method in an invariant.

That's a very interesting observation. Could the solution be running the invariant only once, at the outermost public function call? Hm... It would have to be a runtime feature then, right? Every public function would have calls to the invariant but those calls would have to be elided at runtime. I think... Here is another interesting observation: It is acceptable and quite normal that the object is in limbo state during a public member function. As a consequence, any function that operates on the object must use the object in a write-only fashion during that time frame. This is true even for non-member functions that the object is passed to. So, in theory, even a logging function cannot use the object. Hm... I jokingly propose a new function parameter attribute: 'static out', meaning write-only. ('out' already has a conflicting meaning and 'static' is not overloaded enough yet. :p) Ali
Aug 25 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/26/2013 08:49 AM, deadalnix wrote:
 We simply need to add invariant check in the caller, not the callee, and
 not introduce them if the caller is itself subject to invariant
 insertion when called.

The issue with this is that the invariant is not part of the public interface.
Aug 26 2013
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
26-Aug-2013 20:47, H. S. Teoh пишет:
 On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
 On 08/26/2013 08:49 AM, deadalnix wrote:
 We simply need to add invariant check in the caller, not the callee,
 and not introduce them if the caller is itself subject to invariant
 insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it. But this isn't the first time this is brought up, and it still seems no solution is in sight. :-(

s/solution/implementation/
 T

-- Dmitry Olshansky
Aug 26 2013
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/26/2013 06:47 PM, H. S. Teoh wrote:
 On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
 On 08/26/2013 08:49 AM, deadalnix wrote:
 We simply need to add invariant check in the caller, not the callee,
 and not introduce them if the caller is itself subject to invariant
 insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it. But this isn't the first time this is brought up, and it still seems no solution is in sight. :-( T

(This was not a statement about Ds implementation. The invariant is by design a place to state facts about implementation details.)
Aug 26 2013
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/26/13 9:47 AM, H. S. Teoh wrote:
 On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
 On 08/26/2013 08:49 AM, deadalnix wrote:
 We simply need to add invariant check in the caller, not the callee,
 and not introduce them if the caller is itself subject to invariant
 insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it.

You can attach contracts to an interface. What else would need to be done? Andrei
Aug 26 2013
prev sibling next sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Monday, 26 August 2013 at 06:14:02 UTC, Ali Çehreli wrote:
 On 08/25/2013 05:16 AM, deadalnix wrote:

 The problem is that invariant are checked at the

 function calls. As a consequence, it is impossible to use any

 method in an invariant.

That's a very interesting observation. Could the solution be running the invariant only once, at the outermost public function call? Hm... It would have to be a runtime feature then, right? Every public function would have calls to the invariant but those calls would have to be elided at runtime. I think...

No need for runtime check :D We simply need to add invariant check in the caller, not the callee, and not introduce them if the caller is itself subject to invariant insertion when called.
 Here is another interesting observation: It is acceptable and 
 quite normal that the object is in limbo state during a public 
 member function. As a consequence, any function that operates 
 on the object must use the object in a write-only fashion 
 during that time frame. This is true even for non-member 
 functions that the object is passed to. So, in theory, even a 
 logging function cannot use the object. Hm...

The question of constness of invariant/contracts has been raised, I do think the object should be const, but Andrei think otherwise.
Aug 25 2013
prev sibling next sibling parent "Davidson Corry" <davidsoncorry gmail.com> writes:
On Monday, 26 August 2013 at 06:14:02 UTC, Ali Çehreli wrote:
 On 08/25/2013 05:16 AM, deadalnix wrote:

 The problem is that invariant are checked at the 
 beginning/end on public function calls. As a consequence, it 
 is impossible to use any public
 method in an invariant.

That's a very interesting observation. Could the solution be running the invariant only once, at the outermost public function call? Hm... It would have to be a runtime feature then, right? Every public function would have calls to the invariant but those calls would have to be elided at runtime. I think... Here is another interesting observation: It is acceptable and quite normal that the object is in limbo state during a public member function. As a consequence, any function that operates on the object must use the object in a write-only fashion during that time frame. This is true even for non-member functions that the object is passed to. So, in theory, even a logging function cannot use the object. Hm...

If you will indulge a D newbie, lurker and former Eiffelist: In design-by-contract theory, the invariant is part of the definition of a user-defined type. If type T has an invariant, an object t of that type is not a "real T" if it does not meet the invariant while publicly accessible (that is, while it is not in the hands of a member function of class T). For instance, the purpose of the constructor is to establish the invariant -- some Eiffelists argue that this is the *sole* purpose of the constructor, and that constructors which perform initialization beyond that are overstepping their bounds. For another point, T t should be capable of being tested for its invariant (and passing that test) anywhere and at any time it is publicly accessible. It is only a matter of convenience that implementations test the invariant at entry to and exit from public member functions of class T. That convenience relies on the guarantee that *only* T member functions can be allowed to modify the state of object t -- all other operations in the program must treat t as const (and can, of course, *rely* on t being const)... which, in turn, has all kinds of implications about what you can safely make public about a T object. All of this has implications for D's contract guarantees. For instance, you may not design an invariant that fails on T.init (which is the state of t after you call t.clear() ). In other words, D's object model doesn't necessarily match strict DbC theory. And Jonathon Davis's notion of a t that "isn't really a T" but should be acceptable by T.opAssign() also falls outside the theory. Offhand, I can think of two approaches that might address these desires without too badly weakening the invariant's guarantee: + attach an internal "depth gauge" to each T t. At entry to any public T member function, the depth gauge is incremented; at exit, decremented. Invariant is tested *only* when the depth gauge transitions from or to zero: that is, at its transition from "surfaced" (public) to "submerged" (in the hands of T class functions) or vice-versa. This would allow the implementation to elide any invariant tests while T member functions call other functions, even functions that are themselves public T member functions. * an attribute which tags a public T function as accepting "broken Ts". Such a function would be expected to establish the invariant in the object, and test that invariant, at exit, but would *not* test the invariant at entry. These two overlap but not entirely, and I'm still thinking about whether you would want both, or just one or the other. As I say, this is off the top of my head. I hope I have contributed a useful notion, and not just muddied the waters. Thanks for listening. -- Dai
Aug 26 2013
prev sibling next sibling parent "Paolo Invernizzi" <paolo.invernizzi gmail.com> writes:
On Sunday, 25 August 2013 at 23:02:29 UTC, Andrei Alexandrescu 
wrote:
 On 8/25/13 2:56 PM, Jonathan M Davis wrote:
 On Sunday, August 25, 2013 14:16:28 deadalnix wrote:
 As they are defined now, invariant are plain useless. I find
 myself disabling them one by one in my code as soon as cases 
 get
 outside simple trivia.

 The problem is that invariant are checked at the 
 beginning/end on
 public function calls. As a consequence, it is impossible to 
 use
 any public method in an invariant.

 For instance, just right now I did refactor a struct to use
 bitfields, in order to make it more compact. Now I have to 
 remove
 the invariant as field access are now function calls.

 Another typical situation is when you want to assert certain
 properties in a class hierarchy, where calling virtual method 
 is
 part of the invariant check.

 invariant should (and must to be useful) be inserted at callee
 point, when the callee isn't itself subject to invariant
 insertion, and around public memeber manipulation (when the
 manipulator isn't subject to invariant insertion).

 Any other pattern it doomed to create infinite recursion for 
 non
 trivial invariant checks.

What drives me nuts is the fact that it gets checked when opAssign is called (rather than just when exiting it). That makes it impossible to have a member in an invalid state (either through an init which has an invalid state similar to how floating point values due or because the object hasn't been initialized yet) and have an invariant. At present, emplace will blow up in your face if you try and have an invariant with such types.

emplace() should not call assignment for elaborate types. Andrei

A pull for that is ready, and need review... Just by coincidence I've hit this problem with emplace and invariant and assignment just yesterday, and talked with monarch dodra about the very same issue. https://github.com/D-Programming-Language/phobos/pull/1082 - Paolo Invernizzi
Aug 26 2013
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Monday, 26 August 2013 at 07:20:56 UTC, Davidson Corry wrote:
 ...

By the way it makes me think that invariant should run via CTFE on T.init for all types that have invariant contracts. The fact that default-initialize value may have a broken contract greatly diminishes value of the whole system.
Aug 26 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
 On 08/26/2013 08:49 AM, deadalnix wrote:
We simply need to add invariant check in the caller, not the callee,
and not introduce them if the caller is itself subject to invariant
insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it. But this isn't the first time this is brought up, and it still seems no solution is in sight. :-( T -- Nothing in the world is more distasteful to a man than to take the path that leads to himself. -- Herman Hesse
Aug 26 2013
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Monday, 26 August 2013 at 14:16:04 UTC, Dicebot wrote:
 On Monday, 26 August 2013 at 07:20:56 UTC, Davidson Corry wrote:
 ...

By the way it makes me think that invariant should run via CTFE on T.init for all types that have invariant contracts. The fact that default-initialize value may have a broken contract greatly diminishes value of the whole system.

Not all invariants can run at compile time.
Aug 26 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Aug 26, 2013 at 07:20:21PM +0200, Timon Gehr wrote:
 On 08/26/2013 06:47 PM, H. S. Teoh wrote:
On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
On 08/26/2013 08:49 AM, deadalnix wrote:
We simply need to add invariant check in the caller, not the callee,
and not introduce them if the caller is itself subject to invariant
insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it. But this isn't the first time this is brought up, and it still seems no solution is in sight. :-( T

(This was not a statement about Ds implementation. The invariant is by design a place to state facts about implementation details.)

Oh. I misunderstood what you said, then. T -- The two rules of success: 1. Don't tell everything you know. -- YHL
Aug 26 2013
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Monday, 26 August 2013 at 16:49:01 UTC, Peter Alexander wrote:
 On Monday, 26 August 2013 at 14:16:04 UTC, Dicebot wrote:
 On Monday, 26 August 2013 at 07:20:56 UTC, Davidson Corry 
 wrote:
 ...

By the way it makes me think that invariant should run via CTFE on T.init for all types that have invariant contracts. The fact that default-initialize value may have a broken contract greatly diminishes value of the whole system.

Not all invariants can run at compile time.

True, but it will at least make a notable cluster of potential issues into compile-time errors. Actually, I'd expect non-CTFE invariant to be quite rare - those rarely do any I/O or some weird pointer forging. If invariant is not CTFE-able it should be deferred to runtime to the initial point of the aggregate construction.
Aug 26 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Aug 26, 2013 at 11:01:55AM -0700, Andrei Alexandrescu wrote:
 On 8/26/13 9:47 AM, H. S. Teoh wrote:
On Mon, Aug 26, 2013 at 06:16:25PM +0200, Timon Gehr wrote:
On 08/26/2013 08:49 AM, deadalnix wrote:
We simply need to add invariant check in the caller, not the
callee, and not introduce them if the caller is itself subject to
invariant insertion when called.

The issue with this is that the invariant is not part of the public interface.

The fact that contracts are not part of the public interface greatly limits the usefulness of DbC in D. If they *were* part of the public interface, there'd be more options to improve it.

You can attach contracts to an interface. What else would need to be done?

Sorry, I meant the API, not the OO 'interface'. IIRC, there is a problem with precompiled shared libraries, because the contracts are compiled into the callee rather than the caller, so there is no way for user code to specify whether or not the contracts will be compiled in. If the library was compiled with -release, then no contracts will be included even if the user code was compiled without -release; if the library was compiled without -release, then the contracts will be included even if the user code was compiled with -release. This forces the library vendor to have to ship two versions of the binaries, one compiled with -release, one not. T -- Кто везде - тот нигде.
Aug 26 2013
prev sibling next sibling parent "Dicebot" <public dicebot.lv> writes:
On Monday, 26 August 2013 at 18:15:25 UTC, H. S. Teoh wrote:
 This forces the library vendor to have to ship two versions of 
 the
 binaries, one compiled with -release, one not.

I thought it is a common practice - contracts are not the only troublemakers here, same goes, for example, for debug symbols or bounds checks.
Aug 26 2013
prev sibling next sibling parent "Christopher Bergqvist" <spambox0 digitalpoetry.se> writes:
On Monday, 26 August 2013 at 18:25:21 UTC, Dicebot wrote:
 On Monday, 26 August 2013 at 18:15:25 UTC, H. S. Teoh wrote:
 This forces the library vendor to have to ship two versions of 
 the
 binaries, one compiled with -release, one not.

I thought it is a common practice - contracts are not the only troublemakers here, same goes, for example, for debug symbols or bounds checks.

Why not have the compiler generate internal versions of all public methods of classes with invariants, which do not perform invariant checking, but contain the actual function body? class Foo { private int bar() const { return 5; } public int baz() { return bar() * bar(); } public int third() { return baz() * 2; } invariant() { assert(bar() < 6); } } ..gets translated into.. class Foo { private int bar() const { return 5; } public int baz() { invariant(); int r = __internal_baz(); invariant(); return r; } private int __internal_baz() { return bar() * bar(); } public int third() { invariant(); int r = __internal_third(); invariant(); return r; } private int __internal_third() { return baz() * 2; } invariant() { assert(bar() < 6); } } And then in release builds simply avoid doing this generation of __internal_ methods, keeping the ABI (int baz() and int third()) the same. A variation would be to have __internal_s call __internal_s: private int __internal_third() { return __internal_baz() * 2; } but that diminishes ease of debugging. Anyway, this kind of code transformation is probably impractical for a number of reasons. I just felt like bikeshedding.
Aug 26 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Aug 26, 2013 at 11:47:07PM +0200, Christopher Bergqvist wrote:
[...]
 Why not have the compiler generate internal versions of all public
 methods of classes with invariants, which do not perform invariant
 checking, but contain the actual function body?
 
 class Foo
 {
   private int bar() const { return 5; }
   public int baz() { return bar() * bar(); }
   public int third() { return baz() * 2; }
   invariant()
   {
     assert(bar() < 6);
   }
 }
 
 ..gets translated into..
 
 class Foo
 {
   private int bar() const { return 5; }
   public int baz() { invariant(); int r = __internal_baz();
 invariant(); return r; }
   private int __internal_baz() { return bar() * bar(); }
   public int third() { invariant(); int r = __internal_third();
 invariant(); return r; }
   private int __internal_third() { return baz() * 2; }
   invariant()
   {
     assert(bar() < 6);
   }
 }
 
 And then in release builds simply avoid doing this generation of
 __internal_ methods, keeping the ABI (int baz() and int third()) the
 same.
 
 A variation would be to have __internal_s call __internal_s:
   private int __internal_third() { return __internal_baz() * 2; }
 but that diminishes ease of debugging.
 
 Anyway, this kind of code transformation is probably impractical for
 a number of reasons. I just felt like bikeshedding.

I like this idea, actually. In fact, it did occur to me when skimming over this thread. I don't see why this code transformation would be impractical -- D *is* quite big on templates and compile-time code generation, after all. :) T -- If you think you are too small to make a difference, try sleeping in a closed room with a mosquito. -- Jan van Steenbergen
Aug 26 2013
prev sibling parent Peter Williams <pwil3058 bigpond.net.au> writes:
On 25/08/13 22:16, deadalnix wrote:
 As they are defined now, invariant are plain useless. I find myself
 disabling them one by one in my code as soon as cases get outside simple
 trivia.

 The problem is that invariant are checked at the beginning/end on public
 function calls. As a consequence, it is impossible to use any public
 method in an invariant.

 For instance, just right now I did refactor a struct to use bitfields,
 in order to make it more compact. Now I have to remove the invariant as
 field access are now function calls.

 Another typical situation is when you want to assert certain properties
 in a class hierarchy, where calling virtual method is part of the
 invariant check.

 invariant should (and must to be useful) be inserted at callee point,
 when the callee isn't itself subject to invariant insertion, and around
 public memeber manipulation (when the manipulator isn't subject to
 invariant insertion).

 Any other pattern it doomed to create infinite recursion for non trivial
 invariant checks.

I just discovered another (surprising to me) shortcoming with the implementation of invariants: namely they don't get checked during the default initiation of a struct. By default, I mean the initialization process that you get when you don't provide a this() method. This probably doesn't matter in the grand scheme of things as I imagine an instance of the struct that breaks the invariant cant be used without triggering a check. However, for me, it meant I had to use two statements in my unit tests (instead of one) to test the invariant. Also, the deferred triggering of the invariant could make debugging more difficult. Peter
Aug 26 2013