conformal / gotk3

Go bindings for GTK3
ISC License
471 stars 84 forks source link

How to properly handle interfaces? #6

Open dradtke opened 11 years ago

dradtke commented 11 years ago

In particular I'm interested in implementing the GtkBuildable interface, which is implemented by a crap-ton of things. However, my initial attempt based on existing code to implement it for Widget results in a mess of "ambiguous selector v.GObject" and "too few values in struct initializer" errors.

Thinking about it some more, I'm not sure why it wouldn't be feasible to just create an IBuildable interface implementing toBuildable(), then define that for each object that implements it. Is there a reason that, for example, ComboBox is implemented the way that it is with the CellLayout anonymous field? Is having ICellLayout not enough?

jrick commented 11 years ago

IBuildable sounds like the right way to go about this. I originally implemented these GInterfaces that way with the anonymous fields because I didn't want to reimplement a million different functions for each interface, which all performed the exact same code, but taking different types as the receiver. Keeping, for example, the CellLayout type as a struct, with all its normal implemented fields, and then adding the go interface ICellLayout to everything which implements sounds like a much better way to go about this. It would also simplify all of the new wrap*() functions.

dradtke commented 11 years ago

Looking at it some more I understand better some of the original decisions, and it might actually be best to stick with the struct and interface model, but update the wrap methods. For example:

type Widget struct {
    glib.InitiallyUnowned
    // Interfaces
    *Buildable
}

type Buildable struct {
    obj *glib.Object
}

type IBuildable interface {
    GetName() string
}

func (b *Buildable) GetName() {
    // sole implementation of gtk_buildable_get_name()
    ...
}

And then the wrap methods could be implemented kind of like

func wrapWidget(obj *glib.Object) *Widget {
    w := new(Widget)
    w.InitiallyUnowned = &glib.InitiallyUnowned{obj}
    w.Buildable = &Buildable{obj}
    return w
}

// example of subclassing Widget
func wrapContainer(obj *glib.Object) *Container {
    c := new(Container)
    c.Widget = *wrapWidget(obj)
    return c
}

This code isn't really tested and so might contain some errors, but it should highlight the approach that I have in mind, which would hopefully prevent duplication of interface functionality.

jrick commented 11 years ago

Oh I remember why I had chosen to do it that way originally. If some object implements Buildable and provides function GetName(), I want to be able to call obj.GetName() instead of using obj.GetBuildable().GetName(). If Go allowed defining functions with interfaces as receivers, this wouldn't be an issue.

dradtke commented 11 years ago

I came up with an example for a solution in my fork. Basically I added *Buildable as a field to Widget, then updated all of the wrap methods to pass the new struct up to the parent wrap function. This means that Widget and anything that subclasses it will contain all of the methods of Buildable directly. It might need some more tweaking, but it looks like it would work.