On Nov 12, 2013, at 1:22 PM, Patrick Walton <pcwal...@mozilla.com> wrote:

> On 11/13/13 3:57 AM, Kevin Ballard wrote:
>> On Nov 11, 2013, at 11:43 PM, Patrick Walton <pcwal...@mozilla.com
>> <mailto:pcwal...@mozilla.com>> wrote:
>> 
>>> We considered Go's anonymous fields but rejected them because they
>>> don't support virtual methods at the same time as field embedding.
>> 
>> I don’t follow. Why do Go’s anonymous fields not support virtual methods
>> at the same time as field embedding?
> 
> I want to write this but I can't:
> 
>     [elided for brevity]
> 
>    func main() {
>        myArray := []*A {
>            (&B { A: A { x: 1 }, y: 2 }).Upcast(),
>            (&C { A: A { x: 3 }, z: 4 }).Upcast(),
>        }
>        for i := 0; i < 2; i++ {
>            myArray[i].Foo()
>        }
>    }
> 
> Error:
> 
>    prog.go:41: myArray[i].Foo undefined (type *A has no field or method Foo)
> 
> You can't really write the thing you need to write inside `Upcast` to make 
> this code work. You would need to use an interface instead, but then 
> interfaces don't support shared fields, just like in Rust, leading to the 
> same problem.

Don’t you need a trait in Rust in order to have virtual methods anyway? So the 
same fundamental problem is there; the machinery to support virtual methods 
does not support shared fields.

The trick with the proposed single inheritance model is that traits are 
modified so they can access shared field, by virtue of “extending a struct”. 
Presumably this is rather simple with single inheritance, because you know all 
substructs have the super as a prefix, and thus the trait implementation can 
ignore all the extra fields in the substruct.

But I don’t see why this can’t also be done with embedded fields. You lose the 
prefix property, but I don’t think it’s necessary to implement this. The 
virtual table would just need to store the field offset for the sub-struct that 
provides the method, so when you call a virtual method, you can “slice” the 
parent object (to use Go’s terminology) such that the method’s receiver is the 
sub-struct itself.

Here’s a sample:

struct A {
    x: int
}

struct B {
    y: int
}

struct C {
    A,
    B,
    z: int
}

trait ATrait : A {
    fn foo(&self);
}

trait BTrait: B {
    fn bar(&self);
}

impl ATrait for A {
    fn foo(&self) {
        println!("This is A: {}", self.x);
    }
}

impl BTrait for B {
    fn bar(&self) {
        println!("This is B: {}", self.y);
    }
}

fn main() {
    let myAs = [A{ x: 1 } as ~ATrait,
                C{ A: A{ x: 2 }, B: B{ y: 3 }, z: 4 } as ~ATrait];
    for a in myAs.iter() {
        a.foo();
    }
    let myBs = [B{ y: 1 } as ~BTrait,
                C{ A: A{ x: 2 }, B: B{ y: 3 }, z: 4} as ~BTrait];
    for b in myBs.iter() {
        b.bar();
    }
}

In the case of calling b.bar() on the C object, this would be equivalent to 
b.B.bar().

I think it’s reasonable that traits could still only extend a single struct (I 
believe that relaxing this would require an extra deref on every field access, 
which is no fun). The point is that trait implementations know the type they’re 
being implemented on, and therefore having non-prefixed sub-structs is not a 
problem.

The only penalty this approach has (that comes to mind) over the 
single-inheritance model is that, because of the loss of the prefix property, 
any field access on the trait object itself will require looking up the field 
offset in the virtual table. But trait objects already use the virtual table 
for everything else, and the actual trait implementations don’t require this, 
so I don’t think it’s problematic to require a virtual table lookup on trait 
object field access.

-Kevin
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to