Thread overview
toString() through interface
Apr 20, 2014
David Held
Apr 20, 2014
Adam D. Ruppe
Apr 20, 2014
David Held
Apr 20, 2014
David Held
Apr 20, 2014
bearophile
April 20, 2014
interface Foo { }

class Bar : Foo
{
    override string toString() pure const { return "Bar"; }
}

void main()
{
    Foo foo = new Bar;
    foo.toString();
}

src\Bug.d(14): Error: no property 'toString' for type 'Bug.Foo'

Since all implementations of an interface must derive from Object, why can't we access Object's methods through the interface?  Even explicitly casting to Object doesn't help:

    cast(Object)(foo).toString();

Same error message.  Now here is where things get weird:

    Object baz = foo;

src\Bug.d(15): Error: cannot implicitly convert expression (foo) of type Bug.Foo to object.Object

Orly?!?  I'm pretty sure that almost every other language with interfaces and a singly-rooted object hierarchy has implicit conversions from an interface to the base object type.  Oddly enough, an explicit cast "fixes" things:

    Object baz = cast(Object) foo;
    baz.toString(); // OK

What doesn't make sense is why the inline cast of foo is not allowed, even though it seems to me that it should have the exact same effect. Am I missing something here?

Dave
April 20, 2014
On Sunday, 20 April 2014 at 00:35:30 UTC, David Held wrote:
> Since all implementations of an interface must derive from Object

That's not true. They can also come from IUnknown or a C++ interface.

>     cast(Object)(foo).toString();

(cast(Object)foo).toString() might work


This might return null tho if it is a non-D object through the interface!
April 20, 2014
On 4/19/2014 5:35 PM, David Held wrote:
> interface Foo { }
>
> class Bar : Foo
> {
>      override string toString() pure const { return "Bar"; }
> }
>
> void main()
> {
>      Foo foo = new Bar;
>      foo.toString();
> }

To make things more interesting, consider the call to toString() from inside a class (which is closer to my actual use case):

class Baz
{
    override string toString() pure const
    { return cast(Object)(foo).toString(); }

    Foo foo;
}

This really makes the compiler go bonkers:

src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
src\Bug.d(11): Error: no property 'toString' for type 'Bug.Foo'
src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
src\Bug.d(11): Error: need 'this' for 'foo' of type 'Bug.Foo'

Apparently, DMD features "high-availability error reporting", because the first message might not be received by the programmer, so it writes it again, then gives you a different message, and writes it one more time, just in case there was a communication error or some other fault preventing you from receiving this important message about access to 'foo'.

Again, the cast appears to do absolutely nothing, as the compiler insists on looking up toString() in Foo instead of Object.  What is really peculiar is that message 1, 2, and 4 are complaining that Baz.toString() is not allowed to access foo because it is mutable.  And yet, the 5th error message tells us how to fix it: just add 'this.':

    override string toString() pure const
    { return cast(Object)(this.foo).toString(); }

Now we just get this:

src\Bug.d(11): Error: no property 'toString' for type 'const(Foo)'

Hmm...so adding 'this.' changes a field from "unacceptably mutable" to "just fine for this pure const method".  Isn't that weird?!?  Note also how the error message has subtly changed from "Bug.Foo" to "const(Foo)", because the compiler is still stubbornly insisting on ignoring the cast to 'Object'.  Is there a rational explanation for all this behavior??

Dave

April 20, 2014
On 4/19/2014 5:45 PM, Adam D. Ruppe wrote:
> On Sunday, 20 April 2014 at 00:35:30 UTC, David Held wrote:
>> Since all implementations of an interface must derive from Object
>
> That's not true. They can also come from IUnknown or a C++ interface.

Ok, that's a good reason!

>>     cast(Object)(foo).toString();
>
> (cast(Object)foo).toString() might work
>
>
> This might return null tho if it is a non-D object through the interface!

Yes, I later discovered that I didn't understand the cast syntax properly.  This does indeed work, but leads to the discovery that Object.toString() is not pure. :(  But that's tolerable.  Also, declaring toString() in the interfaces avoids the need for a cast entirely, although I feel this is a bit of a dirty hack.

Dave

April 20, 2014
David Held:

> class Baz
> {
>     override string toString() pure const
>     { return cast(Object)(foo).toString(); }
>
>     Foo foo;
> }
>
> This really makes the compiler go bonkers:
>
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: no property 'toString' for type 'Bug.Foo'
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: need 'this' for 'foo' of type 'Bug.Foo'
>
> Apparently, DMD features "high-availability error reporting", because the first message might not be received by the programmer, so it writes it again, then gives you a different message, and writes it one more time, just in case there was a communication error or some other fault preventing you from receiving this important message about access to 'foo'.

Your code is probably wrong, but those error messages seem too many. So I suggest you to open a little bug report, asking for less error messages :-)


> Is there a rational explanation for all this behavior??

Try also:

    override string toString() const {
        return (cast(Object)foo).toString();
    }


You still have to understand some things about the D objects and interfaces, their standard methods, and what const and purity do in D.

Bye,
bearophile
April 21, 2014
On Sat, 19 Apr 2014 20:45:50 -0400, Adam D. Ruppe <destructionator@gmail.com> wrote:

> On Sunday, 20 April 2014 at 00:35:30 UTC, David Held wrote:
>> Since all implementations of an interface must derive from Object
>
> That's not true. They can also come from IUnknown or a C++ interface.

This is also not true. Only interfaces derived from IUnknown point to a COM object. Only interfaces marked as extern(C++) can point to a C++ object.

Any interface that is purely D MUST point at a D object that derives from Object, EVEN D-defined COM or C++ objects (the latter doesn't really exist).

His code should compile IMO.

>     cast(Object)(foo).toString();
>
> (cast(Object)foo).toString() might work
>
>
> This might return null tho if it is a non-D object through the interface!

As stated above, this is not possible.

-Steve
April 21, 2014
On Sat, 19 Apr 2014 20:51:49 -0400, David Held <dmd@wyntrmute.com> wrote:

> On 4/19/2014 5:35 PM, David Held wrote:
>> interface Foo { }
>>
>> class Bar : Foo
>> {
>>      override string toString() pure const { return "Bar"; }
>> }
>>
>> void main()
>> {
>>      Foo foo = new Bar;
>>      foo.toString();
>> }
>
> To make things more interesting, consider the call to toString() from inside a class (which is closer to my actual use case):
>
> class Baz
> {
>      override string toString() pure const
>      { return cast(Object)(foo).toString(); }
>
>      Foo foo;
> }
>
> This really makes the compiler go bonkers:
>
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: no property 'toString' for type 'Bug.Foo'
> src\Bug.d(11): Error: pure nested function 'toString' cannot access mutable data 'foo'
> src\Bug.d(11): Error: need 'this' for 'foo' of type 'Bug.Foo'
>
> Apparently, DMD features "high-availability error reporting", because the first message might not be received by the programmer, so it writes it again, then gives you a different message, and writes it one more time, just in case there was a communication error or some other fault preventing you from receiving this important message about access to 'foo'.
>
> Again, the cast appears to do absolutely nothing, as the compiler insists on looking up toString() in Foo instead of Object.  What is really peculiar is that message 1, 2, and 4 are complaining that Baz.toString() is not allowed to access foo because it is mutable.  And yet, the 5th error message tells us how to fix it: just add 'this.':
>
>      override string toString() pure const
>      { return cast(Object)(this.foo).toString(); }
>
> Now we just get this:
>
> src\Bug.d(11): Error: no property 'toString' for type 'const(Foo)'


To explain what the compiler is having trouble with, you have to understand precedence.

cast(Object) does not come before '.'

So what the compiler thinks you said is:

return cast(Object)(this.foo.toString());

Others have said how to fix it. I just wanted to point out why you need to fix it that way.

-Steve