Thread overview
D is about to take a wrong turn with interface identities
Nov 20, 2023
Quirin Schroll
Nov 20, 2023
Adam D Ruppe
Nov 27, 2023
Quirin Schroll
Nov 27, 2023
Kagamin
Dec 12, 2023
Quirin Schroll
November 20, 2023

The spec on identity expressions was recently changed to:

>

For class / interface objects, identity is defined as the object references being identical. Null class objects can be compared with is. Note that inferface objects need not have the same reference of the class they were cast from. To test whether an interface shares a class instance with another interface / class value, cast both operands to Object before comparing with is.

My sense is that this behavior is surprising and therefore unacceptable. Even the route of simply casting to void* is unacceptable as it will surprise C++ users who know that dynamic_cast<void*>, given a pointer to a base-type subobject, returns a void* to the actual (most-derived) object; that way, two seemingly unrelated class-type objects can be compared for identity without knowing their most-derived type. D doesn’t need that, given that Object is a known base class for all classes, a cast to Object achieves the same. If anything, D’s cast(void*) should do the same as it does in C++, or be invalid. As was pointed out to me, a type paint should be done as per *cast(void**)(&obj). DRuntime or Phobos could provide this via a convenience function reinterpretAsVoidPointer.

If interface identity cannot be done fast enough and correctly, and the language designers don’t want to provide a slow and correct solution as an operator, whatever their rationale may be, they should not provide an incorrect solution, but rather no solution at all, that is, make using the identity operator with one or two interface type values an error. The error message should tell people to use a cast(Object); for template code, if a value is a class or interface, note that cast(Object) is a no-op on class values.

None of this addresses the problem that the cast(Object) cannot work for extern(C++) classes, even if C++-RTTI was available, which it hopefully will in the future. A solution would be to do what C++ does: Make cast(void*) bypass a potential opCast (akin to how class-type assignment bypasses opAssign) to return a pointer to the most-derived object the reference refers to. Alternatively, a DRuntime function dynamicCastVoidPointer could provide this.

Make interfaces easy to use correctly and hard to use incorrectly. – Scott Mayers

November 20, 2023

On Monday, 20 November 2023 at 16:41:46 UTC, Quirin Schroll wrote:

>

My sense is that this behavior is surprising and therefore unacceptable.

It has been this way for twenty years. Has it bothered you before now?

November 27, 2023

On Monday, 20 November 2023 at 18:39:44 UTC, Adam D Ruppe wrote:

>

On Monday, 20 November 2023 at 16:41:46 UTC, Quirin Schroll wrote:

>

My sense is that this behavior is surprising and therefore unacceptable.

It has been this way for twenty years.

But it was a bug!

>

Has it bothered you before now?

My personal experience with it doesn’t really matter here, but yes, I ran into this.

November 27, 2023

It's not obvious why cast(void*) should cast to Object. Any application that requires accurate pointers would fail horribly.

December 12, 2023

On Monday, 27 November 2023 at 12:08:51 UTC, Kagamin wrote:

>

It's not obvious why cast(void*) should cast to Object. Any application that requires accurate pointers would fail horribly.

Well, C++ does it, basically. D currently doesn’t support C++ RTTI, but it should not make decisions that will make it impossible to do so. For an extern(C++) interface, how else would you test for identity?

As has been pointed out to me, “accurate pointers,” i.e. type paint, cannot be generically expected to work using cast(void*) because of opCast. Use:

R reinterpretCast(R, T)(auto ref T t)
{
    *cast(R*)cast(void**)&t
}

If anything, cast(void*) should do the exact same thing as dynamic_cast<void*> does in C++, at least for extern(C++) classes, and if we want to be consistent – and there’s no reason not to –, it should do the same also for extern(D) classes.