www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - assert(obj) is a mystery

reply Davidson Corry <davidsoncorry comcast.net> writes:
Over on the digitalmars.D newsgroup,
On 11/8/2011 2:35 PM, Alex Rønne Petersen wrote (in a thread
entitled "assert(obj) is an atrocity"):
 Hi,

 As the title suggests, I'm going to be rather blunt about this.
 assert(obj) testing the invariant *without* doing a null check is insane
 for the following reasons:

 1) It is not what a user expects. It is *unintuitive*.
 2) assert(!obj) does an is-null check. assert(obj) is a completely
 broken opposite of this.
 3) No AssertError is thrown, which is the entire point of the built-in
 assert().
 4) The few added instructions for the null check hardly matter in a
 *debug* build of all things.

 I don't mind assert(obj) testing the invariant of obj. In fact, that
 very much makes sense. But please, please, *please* check the object for
 null first. This is a random inconsistency in the language with no other
 justification than "seg faults are convenient in a debugger". By the
 same logic, we might as well not have array bounds checks. However, the
 state of things is that array bounds checks are emitted by default and
 users can disable them for e.g. a release build. I don't see why this
 case is any different.

 - Alex

OK. Not addressing Alex's objections at all, it's not clear to me why anyone would *need* to test the invariant of an object. Aside from the fact that, anytime you actually use the object, its invariant will get tested (twice)... Unless I misunderstand something about design-by-contract (always a possibility), the invariant of an object holds anytime you have access to an object outside of its public interface. The only times an invariant does *not* hold are (a) while the object is being constructed -- in fact, the *whole purpose* of the constructor is to establish the invariant! +++ (b) while an object is executing "private code", i.e. internally during a method (c) after the object has been destroyed In that light, it seems to me that if you can get a non-null reference to obj at all, its invariant must hold. Stating that another way, if you *can* get a non-null reference to an object whose invariant does not hold, the_language_itself_is_broken! No? What am I missing here? What is the purpose of assert(obj) having semantics beyond assert (obj !is null) ?? -- Davidson +++ The notion that the ONLY purpose of the constructor is to establish the invariant is a strict formalism. In practice, many constructors perform additional initialization beyond invariant-establishment, as a convenience. Such additional initialization could be factored out into separate methods. Also, D's insistence that an object be fully-fledged once each of its member fields receives its .init value -- which could happen if the later stages of a constructor throws, or if a live object's .clear() method is called -- constrains what the invariant can guarantee. For that reason, you need to take care that the constructor does *not* throw, and that .clear() is not called, if you have an object that really isn't "live" without some fancy work having been completed on it.
Nov 08 2011
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, November 08, 2011 21:51:46 Davidson Corry wrote:
 OK. Not addressing Alex's objections at all, it's not clear to me why
 anyone would *need* to test the invariant of an object.

I wouldn't expect it to be something that you'd need to do very often. However, if you give access to the member variables in your class or struct - either directly or indirectly - allowing code outside the type to modify that type's state, then the invariant can be violated. Now, it's arguably bad design to allow such access when using an invariant (if not in general), but it _is_ a case where the invariant can be invalidated. - Jonathan M Davis
Nov 09 2011
parent reply =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <xtzgzorex gmail.com> writes:
On 09-11-2011 11:33, Jonathan M Davis wrote:
 On Tuesday, November 08, 2011 21:51:46 Davidson Corry wrote:
 OK. Not addressing Alex's objections at all, it's not clear to me why
 anyone would *need* to test the invariant of an object.

I wouldn't expect it to be something that you'd need to do very often. However, if you give access to the member variables in your class or struct - either directly or indirectly - allowing code outside the type to modify that type's state, then the invariant can be violated. Now, it's arguably bad design to allow such access when using an invariant (if not in general), but it _is_ a case where the invariant can be invalidated. - Jonathan M Davis

I do think Good Practice (TM) dictates that you shouldn't expose something as mutable if mutating it violates the object's invariant. Instead, you should use a property with preconditions. In light of this, I can actually see why assert(obj) testing the invariant can seem very, very odd. (And I mean, nobody's going to write assert(this) or something like that...) - Alex
Nov 09 2011
parent reply Davidson Corry <davidsoncorry comcast.net> writes:
On 11/9/2011 9:35 AM, Jonathan M Davis wrote:
 On Wednesday, November 09, 2011 05:39 Alex Rønne Petersen wrote:
 On 09-11-2011 11:33, Jonathan M Davis wrote:
 On Tuesday, November 08, 2011 21:51:46 Davidson Corry wrote:
 OK. Not addressing Alex's objections at all, it's not clear to me why
 anyone would *need* to test the invariant of an object.

I wouldn't expect it to be something that you'd need to do very often. However, if you give access to the member variables in your class or struct - either directly or indirectly - allowing code outside the type to modify that type's state, then the invariant can be violated. Now, it's arguably bad design to allow such access when using an invariant (if not in general), but it _is_ a case where the invariant can be invalidated.

I do think Good Practice (TM) dictates that you shouldn't expose something as mutable if mutating it violates the object's invariant. Instead, you should use a property with preconditions. In light of this, I can actually see why assert(obj) testing the invariant can seem very, very odd. (And I mean, nobody's going to write assert(this) or something like that...)

You _might_ write assert(this) if you've just called some private functions and wanted to be sure that the invariant is still valid. Since the entire module has access to private functions and variables, and since private functions do _not_ trigger the invariant, it's quite possible for even a well- written class or struct to get a messed up the variant if the module as a whole has bugs. And even if private were restricted to the type, if you were being thorough, you might want to run the invariant within your type after running some private functions. Still, I wouldn't expect assert(obj) or assert(this) to be common. The only time that I've used it was in debugging when I was trying to figure out when the invariant was invalidated within the type when there was a bug. So, I definitely think that it's useful to be able to explicitly call the invariant, but I'd also expect it to be quite rare.

Thanks, Jonathan. I had forgotten that something outside of the object itself could have mucked about with its innards, disrupting its invariant. That is, after all, the reason why the invariant is tested at entry to any public method: if it were *not* possible for the invariant to have been compromised while the object was "in the wild", there would be no need for the test-at-entry. I think my point still stands, however. Any time you actually use "obj" (by calling one of its public methods) its invariant will be tested, and at that point you will learn that "obj" has been compromised. Conversely, if you have an "obj" that is compromised but you never use it, why would you even *care* about its invariant? As far as I can tell, "assert(obj)" MEANS "test the invariant without using the object". And I don't see the point of that. Alex's original complaint, if I understand it correctly, is that a bare mention of "obj" anywhere else is evaluated as a Boolean "obj !is null". ONLY IN "assert(obj)" does it have the special meaning of "obj !is null && obj.invariant_holds". Even though such special semantics are weird and somewhat counter-intuitive, given that they apply only in this one circumstance, I would be glad to have them *if they bought me something*. But I still don't see what they buy me. -- Davidson p.s. they are also unnecessary in that, if you really want to test an object without using it, simply have its class define a public method with an empty {} body, then call that method on the object to be validated. Since the method is public, it will test the class invariant (twice!) -- a bit tedious to have to define that method, yes, but as you say, the need for such testing should be rare, and thus doesn't (??) justify special syntax rules. IMHO, YMMV, see boxtop for details, not available in all states, insert your favorite disclaimer here...
Nov 09 2011
parent Davidson Corry <davidsoncorry comcast.net> writes:
On 11/9/2011 5:12 PM, Jonathan M Davis wrote:
 As far as I can tell, "assert(obj)" MEANS "test the invariant without
 using the object". And I don't see the point of that.

It's occasionally useful when debugging code - particularly when called from inside of the object rather than externally.
 Alex's original complaint, if I understand it correctly, is that a bare
 mention of "obj" anywhere else is evaluated as a Boolean "obj !is null".
 ONLY IN "assert(obj)" does it have the special meaning of "obj !is null
 &&  obj.invariant_holds".

No, his complaint is that it does obj.invariant_holds _without_ checking for null. What everyone initially expects is that assert(obj) is identical to assert(obj is null) - since that's how it works in all other cases (if, while, etc.). The fact that it runs the invariant isn't really a problem IMHO. Since, the invariant shouldn't be failing, it's just additional overhead, and since it's compiled out in release mode, that really isn't a problem. It's the fact that it doesn't first check for null which makes it so that it not only completely changes the semantics of implicitly converting a class reference to bool but that it will kill your program in cases where the object is null. So, as long as it's changed from just testing the invariant to testing for null and _then_ testing the invariant if it's not null, I think that it's fine. As rare as it may be to need to explicitly test the invariant, having a way to do so is definitely not detrimental to the language. It's just how it's currently implemented that's horribly broken.

Ah. Now I understand. Thanks to all. -- Davidson
Nov 10 2011
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, November 09, 2011 05:39 Alex Rønne Petersen wrote:
 On 09-11-2011 11:33, Jonathan M Davis wrote:
 On Tuesday, November 08, 2011 21:51:46 Davidson Corry wrote:
 OK. Not addressing Alex's objections at all, it's not clear to me why
 anyone would *need* to test the invariant of an object.

I wouldn't expect it to be something that you'd need to do very often. However, if you give access to the member variables in your class or struct - either directly or indirectly - allowing code outside the type to modify that type's state, then the invariant can be violated. Now, it's arguably bad design to allow such access when using an invariant (if not in general), but it _is_ a case where the invariant can be invalidated. - Jonathan M Davis

I do think Good Practice (TM) dictates that you shouldn't expose something as mutable if mutating it violates the object's invariant. Instead, you should use a property with preconditions. In light of this, I can actually see why assert(obj) testing the invariant can seem very, very odd. (And I mean, nobody's going to write assert(this) or something like that...)

You _might_ write assert(this) if you've just called some private functions and wanted to be sure that the invariant is still valid. Since the entire module has access to private functions and variables, and since private functions do _not_ trigger the invariant, it's quite possible for even a well- written class or struct to get a messed up the variant if the module as a whole has bugs. And even if private were restricted to the type, if you were being thorough, you might want to run the invariant within your type after running some private functions. Still, I wouldn't expect assert(obj) or assert(this) to be common. The only time that I've used it was in debugging when I was trying to figure out when the invariant was invalidated within the type when there was a bug. So, I definitely think that it's useful to be able to explicitly call the invariant, but I'd also expect it to be quite rare. - Jonathan M Davis
Nov 09 2011
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Wednesday, November 09, 2011 16:36 Davidson Corry wrote:
 On 11/9/2011 9:35 AM, Jonathan M Davis wrote:
 On Wednesday, November 09, 2011 05:39 Alex Rønne Petersen wrote:
 On 09-11-2011 11:33, Jonathan M Davis wrote:
 On Tuesday, November 08, 2011 21:51:46 Davidson Corry wrote:
 OK. Not addressing Alex's objections at all, it's not clear to me why
 anyone would *need* to test the invariant of an object.

I wouldn't expect it to be something that you'd need to do very often. However, if you give access to the member variables in your class or struct - either directly or indirectly - allowing code outside the type to modify that type's state, then the invariant can be violated. Now, it's arguably bad design to allow such access when using an invariant (if not in general), but it _is_ a case where the invariant can be invalidated.

I do think Good Practice (TM) dictates that you shouldn't expose something as mutable if mutating it violates the object's invariant. Instead, you should use a property with preconditions. In light of this, I can actually see why assert(obj) testing the invariant can seem very, very odd. (And I mean, nobody's going to write assert(this) or something like that...)

You _might_ write assert(this) if you've just called some private functions and wanted to be sure that the invariant is still valid. Since the entire module has access to private functions and variables, and since private functions do _not_ trigger the invariant, it's quite possible for even a well- written class or struct to get a messed up the variant if the module as a whole has bugs. And even if private were restricted to the type, if you were being thorough, you might want to run the invariant within your type after running some private functions. Still, I wouldn't expect assert(obj) or assert(this) to be common. The only time that I've used it was in debugging when I was trying to figure out when the invariant was invalidated within the type when there was a bug. So, I definitely think that it's useful to be able to explicitly call the invariant, but I'd also expect it to be quite rare.

Thanks, Jonathan. I had forgotten that something outside of the object itself could have mucked about with its innards, disrupting its invariant. That is, after all, the reason why the invariant is tested at entry to any public method: if it were *not* possible for the invariant to have been compromised while the object was "in the wild", there would be no need for the test-at-entry. I think my point still stands, however. Any time you actually use "obj" (by calling one of its public methods) its invariant will be tested, and at that point you will learn that "obj" has been compromised. Conversely, if you have an "obj" that is compromised but you never use it, why would you even *care* about its invariant? As far as I can tell, "assert(obj)" MEANS "test the invariant without using the object". And I don't see the point of that.

It's occasionally useful when debugging code - particularly when called from inside of the object rather than externally.
 Alex's original complaint, if I understand it correctly, is that a bare
 mention of "obj" anywhere else is evaluated as a Boolean "obj !is null".
 ONLY IN "assert(obj)" does it have the special meaning of "obj !is null
 && obj.invariant_holds".

No, his complaint is that it does obj.invariant_holds _without_ checking for null. What everyone initially expects is that assert(obj) is identical to assert(obj is null) - since that's how it works in all other cases (if, while, etc.). The fact that it runs the invariant isn't really a problem IMHO. Since, the invariant shouldn't be failing, it's just additional overhead, and since it's compiled out in release mode, that really isn't a problem. It's the fact that it doesn't first check for null which makes it so that it not only completely changes the semantics of implicitly converting a class reference to bool but that it will kill your program in cases where the object is null. So, as long as it's changed from just testing the invariant to testing for null and _then_ testing the invariant if it's not null, I think that it's fine. As rare as it may be to need to explicitly test the invariant, having a way to do so is definitely not detrimental to the language. It's just how it's currently implemented that's horribly broken. - Jonathan M Davis
Nov 09 2011