Thread overview
cas and interfaces
Dec 23, 2018
Johannes Loher
Dec 24, 2018
Johan Engelen
Dec 25, 2018
Johannes Loher
Dec 27, 2018
Rene Zwanenburg
Dec 27, 2018
Johan Engelen
December 23, 2018
I recently played around with atomic operations. While doing so, I noticed a problem with the interaction of interfaces and cas. Consider the following program:

```
import core.atomic;
import std.stdio;

interface TestInterface
{
}

class TestClass : TestInterface
{
}

void main()
{
    shared TestInterface testInterface = new shared TestClass;
    cas(&testInterface, testInterface, new shared TestClass).writeln;
    writeln(typeid(testInterface));
}
```
(https://run.dlang.io/is/9P7PAb)

This program compiles successfully and outputs

```
true
Error: program killed by signal 11
```

i.e. a segmentation fault happens.

When replacing "interface" with "abstract class" (https://run.dlang.io/is/sFaO1k), everything works as expected and the program outputs

```
true
onlineapp.TestClass
```

Is this actually a bug or a fundamental limitation of cas and interfaces? Did I do anything wrong?
December 24, 2018
On Sunday, 23 December 2018 at 14:07:04 UTC, Johannes Loher wrote:
> I recently played around with atomic operations. While doing so, I noticed a problem with the interaction of interfaces and cas. Consider the following program:
>
> ```
> import core.atomic;
> import std.stdio;
>
> interface TestInterface
> {
> }
>
> class TestClass : TestInterface
> {
> }
>
> void main()
> {
>     shared TestInterface testInterface = new shared TestClass;
>     cas(&testInterface, testInterface, new shared TestClass).writeln;
>     writeln(typeid(testInterface));
> }
> ```
> (https://run.dlang.io/is/9P7PAb)

The types of the 2nd and 3rd arguments of `cas` do not have to be the same, and aren't in your case. I think what's happening is that you are overwriting `testInterface` with a pointer to a TestClass which is not a valid TestInterface pointer. And then the program does something invalid because, well, you enter UB land.
Fixed by:
```
cas(&testInterface, testInterface, cast(shared(TestInterface)) new shared TestClass).writeln;
```
Note the cast!

Whether this is a bug in `cas` or not, I don't know. The `cas` template checks whether the 3rd can be assigned to the 1st argument (`*here = writeThis;`) which indeed compiles _with_ an automatic conversion. But then the implementation of `cas` does not do any automatic conversions (casts to `void*`). Hence the problem you are seeing.

-Johan



December 25, 2018
On Monday, 24 December 2018 at 11:23:32 UTC, Johan Engelen wrote:
> On Sunday, 23 December 2018 at 14:07:04 UTC, Johannes Loher wrote:
>> [...]
>
> The types of the 2nd and 3rd arguments of `cas` do not have to be the same, and aren't in your case. I think what's happening is that you are overwriting `testInterface` with a pointer to a TestClass which is not a valid TestInterface pointer. And then the program does something invalid because, well, you enter UB land.
> Fixed by:
> ```
> cas(&testInterface, testInterface, cast(shared(TestInterface)) new shared TestClass).writeln;
> ```
> Note the cast!
>
> Whether this is a bug in `cas` or not, I don't know. The `cas` template checks whether the 3rd can be assigned to the 1st argument (`*here = writeThis;`) which indeed compiles _with_ an automatic conversion. But then the implementation of `cas` does not do any automatic conversions (casts to `void*`). Hence the problem you are seeing.
>
> -Johan

Thanks a lot for the info, that clarifies things a bit. But it still leaves the question, why it works correctly when inheriting from an abstract class instead of implementing an interface... Any idea about why that?
December 27, 2018
On Tuesday, 25 December 2018 at 22:07:07 UTC, Johannes Loher wrote:
> Thanks a lot for the info, that clarifies things a bit. But it still leaves the question, why it works correctly when inheriting from an abstract class instead of implementing an interface... Any idea about why that?

Unlike interfaces, base class references don't need adjustment. You can see this in action by printing addresses:

https://run.dlang.io/is/m6icVr

import std.stdio;

interface I {}
class Base : I {}
class Derived : Base { }

void main()
{
    auto derived = new Derived;
    Base base = derived;
    I i = derived;

    writeln(cast(void*)derived);
    writeln(cast(void*)base);
    writeln(cast(void*)i);
}

> 7FC1F96EE000
> 7FC1F96EE000
> 7FC1F96EE010
December 27, 2018
On Thursday, 27 December 2018 at 12:07:48 UTC, Rene Zwanenburg wrote:
> On Tuesday, 25 December 2018 at 22:07:07 UTC, Johannes Loher wrote:
>> Thanks a lot for the info, that clarifies things a bit. But it still leaves the question, why it works correctly when inheriting from an abstract class instead of implementing an interface... Any idea about why that?
>
> Unlike interfaces, base class references don't need adjustment.

Yeah. You shouldn't need to know these details but if you are interested, the details are here: https://dlang.org/spec/abi.html#classes (it's meant for tech reference, not for explanation. If you need more explanation, go search for vtables, multiple inheritance, etc.).

-Johan