Thread overview
Inheritance and arrays
Jul 03, 2023
Arafel
Jul 03, 2023
FeepingCreature
Jul 03, 2023
Arafel
Jul 03, 2023
Arafel
Jul 03, 2023
FeepingCreature
Jul 03, 2023
Rene Zwanenburg
Jul 03, 2023
Arafel
July 03, 2023
Hi!

I am a D user coming from java, rather than from C/C++ (although obviously also have some exposure to them), and thus apparently one of the few people here who likes OO (within reason, of course).

So while I appreciate the fact that D closely follows java's design, I wonder why there is no implicit inheritance for arrays (also the same applies to AAs):

```d
interface I {}
class C : I {}

void main() {
    I i;
    C c = null;
    i = c; // Works

    I[] ii;
    C[] cc = null;
    // ii = cc; // Doesn't work: Error: cannot implicitly convert expression `cc` of type `C[]` to `I[]`
    ii = cast (I[]) cc; // Works, but why do I need to cast?
}
```

The equivalent java code compiles without issue:

```java
interface I {}
class C implements I {}

public class MyClass {
    public static void main(String args[]) {
        I i;
        C c = null;
        i = c; // Works

        I[] ii;
        C[] cc = null;
        ii = cc; // Also works
    }
}
```

Is this a conscious design decision (if so, why?), or just a leak of some implementation detail, but that could eventually be made to work?
July 03, 2023

On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:

>

Hi!

I am a D user coming from java, rather than from C/C++ (although obviously also have some exposure to them), and thus apparently one of the few people here who likes OO (within reason, of course).

So while I appreciate the fact that D closely follows java's design, I wonder why there is no implicit inheritance for arrays (also the same applies to AAs):

interface I {}
class C : I {}

void main() {
    I i;
    C c = null;
    i = c; // Works

    I[] ii;
    C[] cc = null;
    // ii = cc; // Doesn't work: Error: cannot implicitly convert expression `cc` of type `C[]` to `I[]`
    ii = cast (I[]) cc; // Works, but why do I need to cast?
}

The cast version "works", but will crash at runtime.

In D, as opposed to Java, a reference to an object has a different pointer value than a reference to the interface-typed version of that object. This is necessary for efficient compiled virtual method calls on the interface. But for the same reason, you cannot reinterpret an array of objects to an array of interfaces; even if you can implicitly convert each object to that interface, there's a difference between automatically rewriting a value and automatically rewriting every element of an array: one is O(1), the other is O(n) and incurs a GC allocation.

July 03, 2023

On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:

>

Hi!

I am a D user coming from java, rather than from C/C++ (although obviously also have some exposure to them), and thus apparently one of the few people here who likes OO (within reason, of course).

So while I appreciate the fact that D closely follows java's design, I wonder why there is no implicit inheritance for arrays (also the same applies to AAs):

...

Is this a conscious design decision (if so, why?), or just a leak of some implementation detail, but that could eventually be made to work?

See also this thread on implementation details: https://forum.dlang.org/post/bibtjiiwpiqzzfwgxgdd@forum.dlang.org

July 03, 2023

On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:

>

Is this a conscious design decision (if so, why?), or just a leak of some implementation detail, but that could eventually be made to work?

Besides the pointer adjustment problem mentioned by FeepingCreature, it's an unsound conversion even with just class inheritance. Consider:

class A {}
class B : A {}
class C : A {}

void main()
{
  auto bArr = [new B()];
  A[] aArr = bArr; // If this was allowed..
  aArr[0] = new C(); // This would be a problem, because bArray would now contain a C.
}
July 03, 2023
That's very clearly an implementation detail leaking: the semantics of the language shouldn't depend on how interfaces and classes are implemented.

So then I need to do something like:

```d
ii = cc.map!(a => cast (I) a).array;
```

(I just tested it and it works)

Any reason why it can't be done internally by the language?

I find it most unexpected and confusing and, assuming it won't change anytime soon, I think it should at the very least be **clearly** documented. And the cast forbidden, since it can't possible work between classes and interfaces.

Honestly, this severely limits the usefulness of interfaces, and for hierarchies of classes it might be much better to switch to abstract classes.

BTW, even for (abstract) classes you need a cast:

```d
import std;

abstract class I {
    abstract void foo();
}
class C : I {
    this(int i) {
        this.i = i;
    }
    override void foo() {
        writeln("In foo: ",i);
    }
    int i;
}

void main() {
    I i;
    C c = new C(1);
    i = c; // Works

    I[] ii;
    C[] cc;
    cc ~= c;
    i.foo();
    // ii = cc; // Doesn't work: Error: cannot implicitly convert expression `cc` of type `C[]` to `I[]`
    ii = cast (I[]) cc; // Compiles, apparently works
    //ii = cc.map!(a => cast(I) a).array;
    ii[0].foo();
}
```

Shouldn't `ii = cc` work in this case?

On 3/7/23 12:06, FeepingCreature wrote:
> On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
>> Hi!
>>
>> I am a D user coming from java, rather than from C/C++ (although obviously also have some exposure to them), and thus apparently one of the few people here who likes OO (within reason, of course).
>>
>> So while I appreciate the fact that D closely follows java's design, I wonder why there is no implicit inheritance for arrays (also the same applies to AAs):
>>
>> ```d
>> interface I {}
>> class C : I {}
>>
>> void main() {
>>     I i;
>>     C c = null;
>>     i = c; // Works
>>
>>     I[] ii;
>>     C[] cc = null;
>>     // ii = cc; // Doesn't work: Error: cannot implicitly convert expression `cc` of type `C[]` to `I[]`
>>     ii = cast (I[]) cc; // Works, but why do I need to cast?
>> }
>> ```
>>
> The `cast` version "works", but will crash at runtime.
> 
> In D, as opposed to Java, a reference to an object has a *different pointer value* than a reference to the interface-typed version of that object. This is necessary for efficient compiled virtual method calls on the interface. But for the same reason, you cannot reinterpret an array of objects to an array of interfaces; even if you can implicitly convert each object to that interface, there's a difference between automatically rewriting a value and automatically rewriting every element of an array: one is O(1), the other is O(n) and incurs a GC allocation.

July 03, 2023
On 3/7/23 13:03, Rene Zwanenburg wrote:
> On Monday, 3 July 2023 at 09:50:20 UTC, Arafel wrote:
>> Is this a conscious design decision (if so, why?), or just a leak of some implementation detail, but that could eventually be made to work?
> 
> Besides the pointer adjustment problem mentioned by FeepingCreature, it's an unsound conversion even with just class inheritance. Consider:
> 
> ```
> class A {}
> class B : A {}
> class C : A {}
> 
> void main()
> {
>    auto bArr = [new B()];
>    A[] aArr = bArr; // If this was allowed..
>    aArr[0] = new C(); // This would be a problem, because bArray would now contain a C.
> }
> ```

This is a really good point. I just checked out of curiosity what Java does (because it's allowed there).

TIL it throws [an exception](https://docs.oracle.com/javase/8/docs/api/java/lang/ArrayStoreException.html) at runtime, which I guess is not a viable strategy for D.

Although when using interfaces, if I cast the individual class instances to interfaces, it should work, right?

Because then I'm storing the pointers to the interfaces, not to the actual class, so the arrays are actually different, and not two slices of the same array:


```
import std;

interface I { }
class C : I { }

void main() {
    C c = new C;
	I i = c;

	assert (c is i); // OK, took me a while to notice that "is" is smart enough.
    assert (cast (void *) c != cast (void *) i); // This is what we really care about.

	C[] cc = [ c ];
    I[] ii;
	ii = cc.map!( a => cast (I)a ).array;

	assert (ii[0] is c); // This can be unexpected, even if technically right!
    assert (cast (void*) ii[0] != cast (void*) c); // Now this is what we need.
}
```
July 03, 2023

On 7/3/23 7:37 AM, Arafel wrote:

>

That's very clearly an implementation detail leaking: the semantics of the language shouldn't depend on how interfaces and classes are implemented.

It's a semantic detail -- casting an array does not make a copy, and an array is not a complex object. It's just a block of data.

And so it only succeeds if the binary representation can properly be reinterpreted.

This is the same for all array types, not just class/interfaces:

int i = 1;
long x = i; // ok
int[] a = [1];
long[] b = a; // error

If Java works, it means that Java either handles the conversion by making a copy, or by properly converting on element fetch/store based on type introspection. It also might use a different mechanism to point at interfaces.

You might want it to be similar, but there are tradeoffs. With D, though, there is always the possibility of getting what you want by adding a specialized type. Indeed, if you are just going to read the definition, a map would work:

auto ii = cc.map!(function I(C c) => c);
I i = ii[0]; // ok

However, if you plan to write the definition as well, you need a more specialized type.

It's all doable, though.

>

I find it most unexpected and confusing and, assuming it won't change anytime soon, I think it should at the very least be clearly documented. And the cast forbidden, since it can't possible work between classes and interfaces.

It is documented:
https://dlang.org/spec/arrays.html#implicit-conversions

However, the interface conversion is not covered, and it's a bit nebulous as to what is specifically covered with classes/interface.

I think it could be improved.

>

Honestly, this severely limits the usefulness of interfaces, and for hierarchies of classes it might be much better to switch to abstract classes.

BTW, even for (abstract) classes you need a cast:

...

>

Shouldn't ii = cc work in this case?

I think this has been discussed in another part of the thread. But I did want to point out that an implicit cast to const base type array is possible (not for interfaces, since the binary representation is different).

-Steve

July 03, 2023
On 3/7/23 17:41, Steven Schveighoffer wrote:

> 
> If Java works, it means that Java either handles the conversion by making a copy, or by properly converting on element fetch/store based on type introspection. It also might use a different mechanism to point at interfaces.
> 

As I mentioned in another reply, Java does it by adding a runtime check to the type at insertion. This is a performance hit, and I'm glad it's not done in D.

Also, I think I read in some other thread here that Java uses a single pointer for both objects and interfaces, but I think the biggest issue here is the covariance.

>> I find it most unexpected and confusing and, assuming it won't change anytime soon, I think it should at the very least be **clearly** documented. And the cast forbidden, since it can't possible work between classes and interfaces.

I want to answer to myself here: it was my knowledge of type theory that was lacking: TIL that even if two types are covariant, arrays of them needn't be, and usually aren't. So I was spoiled by Java ;-)

[In case somebody else is interested](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Arrays) (also just google it, there are tons of links on the subject).

On a final note, thanks to everybody who answered! For all its quirks, D is a great language that I enjoy using, and the community here is great, even if at times it could be a bit more optimistic :-)