View mode: basic / threaded / horizontal-split · Log in · Help
November 09, 2011
assert(obj) is a mystery
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.
November 09, 2011
Re: assert(obj) is a mystery
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
November 09, 2011
Re: assert(obj) is a mystery
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
November 09, 2011
Re: assert(obj) is a mystery
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
November 10, 2011
Re: assert(obj) is a mystery
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...
November 10, 2011
Re: assert(obj) is a mystery
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
November 10, 2011
Re: assert(obj) is a mystery
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
Top | Discussion index | About this forum | D home