gotk4 icon indicating copy to clipboard operation
gotk4 copied to clipboard

Casting *glib.Object to non-gtk object requires extra casting step

Open MJacred opened this issue 2 years ago • 10 comments

Specs

  • using gotk4 version: 2d57ba75df52b3a0ff2bf7155d64a3677721a52a (master)

Problem

what I want to achieve:

  • get selected filenames as golang strings from dialog

EDIT: ok, the list model should give *gio.File objects in this case, so no issue here. Though there are probably other cases? if not, this issue can be closed. (code is still untested, as refactoring from gotk3->gotk4 is work in progress)

problem

  • casting gives me Objector, which is nice-to-have for further casting to specific gtk object, but not to e.g. golang primitive types

steps

  • read selected files from open gtk.FileChooserNative using Files() -> returns *gio.ListModel object
  • read list using Item(i) -> retunrs *glib.Object objects
  • using CastType(glib.TypeString) -> returns Objector
  • now I need to do value.(interface{}).(string) to get my string, because string does not implement this interface

Solution

  1. is this the wrong approach? or
  2. can return type of existing Cast*() funcs be changed to interface{}? or
  3. adding alternative Cast*() funcs that return interface{}? (or sth. along the lines)

MJacred avatar Dec 14 '22 23:12 MJacred

now I need to do value.(interface{}).(string) to get my string, because string does not implement this interface

This is really weird and probably doesn't work as well as you expect it to, because string doesn't implement Objector, so it'll crash right in the function.

Are you sure this isn't a GValue or something like that?

diamondburned avatar Dec 15 '22 00:12 diamondburned

Are you sure this isn't a GValue or something like that?

see EDIT in my description -> it is a GValue (placement was not that good, I guess)

Though the primitives are also valid GValues. So it might pose a problem later, if there's a case where the list consists of primitives (haven't found a case, yet)

Closing issue for now?

MJacred avatar Dec 15 '22 00:12 MJacred

Yes, though this is one of those scenarios where it's extremely important to defer to the official GTK API documentation:

According to Gtk.FileChooser.get_files, the type of each object in the returned list is a GFile, not a string! You'll need to do f.CastObject().(gio.Filer) to get the GFile in the most correct way possible (there's no guarantee that the returned type is anything concrete, but it must implement the Filer interface).

Admittedly, the method should be Item(n uint) glib.Objector, not Item(n uint) *glib.Object. In fact, since Go has generics now, we can even have a ListStore[Filer] type. Since the whole package has no stable guarantees at the moment, I might eventually get to changing it to work better.

For now, feel free to close this issue.

diamondburned avatar Dec 15 '22 08:12 diamondburned

Sorry, I should have quoted it directly from my original description (I noticed that it returns *gio.File objects due to the docs and amended it right away):

EDIT: ok, the list model should give *gio.File objects in this case, so no issue here. Though there are probably other cases? if not, this issue can be closed. (code is still untested, as refactoring from gotk3->gotk4 is work in progress)


Admittedly, the method should be Item(n uint) glib.Objector, not Item(n uint) *glib.Object

From what I can see, glib.Objector is never returned from any method, or taken as parameter (except in the new() funcs). The same goes for e.g. gio.ListModeller.

Isn't that why there's func (v *Object) Cast*() Objector? At least if you get an object from the API.

But what about the other way around? As these funcs don't take an interface as input, what is the best approach to pass in an object as parameter? glib.BaseObject()?

Do you intend to change this that the interface shall be taken as input parameter and return value?

MJacred avatar Dec 16 '22 13:12 MJacred

From what I can see, glib.Objector is never returned from any method, or taken as parameter (except in the new() funcs). The same goes for e.g. gio.ListModeller.

That's definitely weird in hindsight. I tried digging through git blame and wasn't able to find a reasonable explanation as to why I did that.

Isn't that why there's func (v *Object) Cast*() Objector? At least if you get an object from the API.

No Cast() is implementation detail at best and broken at worst.

Again, it's important to emphasize that the function doesn't necessarily return a *gio.File, although it may. What it (and the generator) actually does is traversing the type tree:

  • First, is there a Go type that matches the actual object type?
    • If we're in a Google Drive GIO mount, we might have our own GDriveFile type in C.
    • There definitely isn't an appropriate Go type for that within gotk4, so we can't just use that type.
  • Second, can the type that it's inheriting from (its base type) be used instead?
  • Repeat this process up the class inheritance tree. It might go GDriveFile -> GNetworkFile (implements GFile) -> GObject.

A few things to note here:

  • All the above types except for GObject implements GFile, so we can comfortably cast it to any Go type that also implements that interface.
  • In the second to worst case, we can't cast it to any of its class types, so we use our *gio.File type to get the needed methods while still implementing gio.Filer.
  • In the worst case, we do not know of a *gio.File at all, so we have to resort to GObject. This has happened a few times before.
  • The generated code has to be flexible enough to know whether the returned object from Cast() actually satisfies the returning interface type, since it may not always.

FWIW I think it's intentional that the generator generates a ListModel by itself which just returns a GObject with no hint as to what the actual type may be (until later on), so there's not much that it can do. Still, returning an Objector seems to be the better idea here, it'll only add some cost to do type conversion.

diamondburned avatar Dec 16 '22 17:12 diamondburned

Again, it's important to emphasize that the function doesn't necessarily return a *gio.File, although it may […]

Ah, yes, indeed. Thanks for the clarification.


After a simple search I found ~80 instances where Objector should have been returned/taken.

There are similar cases for other types where the interface version should be returned I reckon, such as for func (self *DropDown) Model() *gio.ListModel.

I guess the affected interfaces need to be identified one by one?

MJacred avatar Dec 16 '22 20:12 MJacred

Hmm, in an ideal world, I think we'll generate a type ListModel[T Objector] struct, but I'm not sure how feasible that actually is. I think maybe making it all return Objector is good enougn. Yet another special case in the code generator.

diamondburned avatar Dec 17 '22 00:12 diamondburned

Code generators are black magic either way.

Shall I update issue title & description to reflect the issue of "use interface instead of struct for param/return type, if necessary" or close this one and create new issue?

MJacred avatar Dec 17 '22 12:12 MJacred

I think this issue is fine as it is.

diamondburned avatar Dec 18 '22 21:12 diamondburned

Ok, then I'll leave title and description as-is and we'll leave the issue open until the struct type is replaced by the interface type

MJacred avatar Dec 20 '22 12:12 MJacred