assert(obj) is an atrocity
Alex Rønne Petersen
xtzgzorex at gmail.com
Sat Nov 19 10:02:13 PST 2011
On 13-11-2011 22:38, Jonathan M Davis wrote:
> On Sunday, November 13, 2011 18:52:35 Alex Rønne Petersen wrote:
>> I'd just like to add, regarding asserting invariants: Why not just *make
>> invariants callable*? I.e. obj.invariant() invokes the invariant
>> function. invariant is already a keyword, and invariants are declared
>> like functions, so this seems like a logical thing to do.
>
> Call __invariant. But like all __ functions, I don't think that it's really
> intended to be called directly in code. You can do it though.
>
> Really, I don't think that there's a problem with assert(obj) calling obj's
> invariant. It's just extra overhead in a non-release build which is running a
> set of assertions which should be true anyway (assuming that it's being called
> outside of the class). It's the fact that it doesn't check for null first which
> is so atrocious, since that _completely_ changes the semantics of what obj
> normally does when implicitly converted to bool. If it checked for null first,
> then the normal semantics hold with the addition of checking the invariant.
I ran into a funny issue today. I wanted to assert that all items in a
collection are not null. Naturally, I just did something like:
invariant()
{
foreach (item; _store)
assert(item);
}
That was a bad idea, however! Because this assert calls the invariant of
item, *it can end up causing an infinite loop*. This can happen in a
scenario such as:
class A
{
Collection!B col;
invariant()
{
assert(col);
}
}
class B
{
A a;
invariant()
{
assert(a);
}
}
auto a = new A(); a.col = new Collection!B();
auto b = new B(); b.a = a; a.col.add(b);
assert(b);
The call sequence will now look something like:
1: B.invariant()
2: A.invariant()
3: Collection!B.invariant()
4: *B.invariant()*
5: ... and so on ...
I just thought it was worth mentioning how calling the invariant in
assert can lead to very unexpected results. Of course, the solution is
simple: assert(item !is null); in the collection invariant. Still, I
think this is just as unintuitive as the missing null check.
I think we need to keep it simple here.
>
> The fun part about assert(obj) though is that it doesn't work with structs -
> not directly anyway. If obj is a struct, assert(obj) tries to convert it to
> bool like it would do normally. However, assert(&obj) _does_ call the
> invariant. Pointers to structs are treated exactly the same as references to
> classes in this case (complete with the lack of a null check). So, it's
> arguably consistent, but it's a bit weird.
>
> In any case, what really needs to be done is to add the extra null check. It
> won't silently break code to do that, and it'll avoid the surprise and
> confusiong of assert(obj) not checking for null and blowing up as a result.
>
> - Jonathan M Davis
- Alex
More information about the Digitalmars-d
mailing list