You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
I have a proposal that I think would make it easier to take advantage of the useful getter functions this library generates for fields. Consider this simple example, using optional: pointer (or individual pointer: true directives on optional fields):
Schema
typeFood {
name: String! # Zero is a valid value if someone really loves celery, so using value optionals isn't appropriatecalories: Int!
}
typePerson {
name: String! # If the Person doesn't have a favourite food, the API returns null for this entire fieldfavouriteFood: Food
}
typeQuery {
person(name: String): Person
}
One of the things genqlient will generate is a getter method for favouriteFood.name:
// GetName returns FindPersonPersonFavouriteFood.Name, and is useful for accessing the field via an interface.func (v*FindPersonPersonFavouriteFood) GetName() string { returnv.Name }
My interpretation of the comment on the getter is that over time, I may find myself with many types having a name field, and might want to start writing functions that can deal with any of them. So, I might want to write something like:
typenamedinterface {
GetName() string
}
funcprocessName(fnamed) *string {
iff==nil { returnnil }
name:=f.GetName()
// Do some processing involving the namereturn&name
}
Of course, I've now shot myself in the foot, because f is a typed nil; the f == nil comparison will return false, and I'll get a nil pointer dereference when I call GetName.
Describe the solution you'd like
I think this could be made smoother if the getters generated for fields of a pointer-optional nested type themselves performed a nil-check and returned pointers (while leaving the actual field in the struct a value, unless it itself was optional). i.e., the generated getter from above would instead be:
// GetName returns FindPersonPersonFavouriteFood.Name, and is useful for accessing the field via an interface.func (v*FindPersonPersonFavouriteFood) GetName() *string {
ifv==nil { returnnil }
return&v.Name
}
Now, the call to f.GetName() would return a nil pointer instead of panicking, and I could return that from my function directly. I would probably also want to remove the f == nil check as it never really served a purpose anyway:
funcprocessName(fnamed) *string {
name:=f.GetName()
// Do some processing involving the namereturnname
}
As far as I can tell, genqlient always generates separate Go types per-query per-type, so I don't believe this would have any negative effects in cases where the nested type is optional in one parent type and non-optional in another.
This would also support the common response when this behaviour of Go is questioned as a design flaw, which is that the implementer of the methods on an interface should be responsible for handling nil pointers. Or, to say it another way, the getter says it can return a string given any pointer to FindPersonPersonFavouriteFood, but currently if that pointer is nil it cannot.
Of course, there's the fairly big caveat that genqclient hasn't said it implements any interfaces; I have 😁 But I believe such a use case was the intent behind the feature?
Describe alternatives you've considered
I appreciate that there are several reasons the maintainers may not want to implement this, for example:
It's 100% a breaking change
The library already supports unmarshalling nulls to a user-provided generic type, which in an ideal world is preferable to the imperfect modelling of missing values using Go nils
Reflection can be used to check if the concrete value is nil rather than == nil
I could nil-check the pointer before I pass it to my function
Nevertheless, because it's perfectly valid in Go to call a method on a (typed) nil pointer, I think my suggestion is overall more type-safe if the getters are pointer receivers rather than value receivers.
Additional context
While Go is not my first language, I would be happy to look into implementing this change if there is any interest in it!
The text was updated successfully, but these errors were encountered:
because p.Person.FavoriteFood is (*FindPersonPersonFavouriteFood)(nil)? I agree that it's reasonable to want that to work.
Unfortunately I'm not sure if changing the return to a pointer automatically is really reasonable; the field isn't optional by the time we get there; and it's a bit weird (and maybe conflicting, in the case where the type is a fragment used in several places?) to have a pointer dependent on the type of the parent field. For example, if you make person required in your query, now name is a pointer in one place and not the other, so you can no longer use a common interface (We'd also have to put it behind an option for compat, which is a bit awkward for something this specific.)
What we might be able to do is: in the generated getter methods, if both the receiver and return are already pointers (receiver probably always is), generate the nil-checks. Then you could at least ask for a pointer there and get what you want. It's a bit of an awkward foot-gun, but it seems like a safe enough change to make (I should hope no one is depending on the fact that that panics) and at least solves part of the problem. Do you want to try a PR to see how that looks?
Is your feature request related to a problem? Please describe.
I have a proposal that I think would make it easier to take advantage of the useful getter functions this library generates for fields. Consider this simple example, using
optional: pointer
(or individualpointer: true
directives on optional fields):Schema
Query
One of the things genqlient will generate is a getter method for
favouriteFood.name
:My interpretation of the comment on the getter is that over time, I may find myself with many types having a
name
field, and might want to start writing functions that can deal with any of them. So, I might want to write something like:Of course, I've now shot myself in the foot, because
f
is a typed nil; thef == nil
comparison will return false, and I'll get a nil pointer dereference when I callGetName
.Describe the solution you'd like
I think this could be made smoother if the getters generated for fields of a pointer-optional nested type themselves performed a nil-check and returned pointers (while leaving the actual field in the struct a value, unless it itself was optional). i.e., the generated getter from above would instead be:
Now, the call to
f.GetName()
would return a nil pointer instead of panicking, and I could return that from my function directly. I would probably also want to remove thef == nil
check as it never really served a purpose anyway:As far as I can tell, genqlient always generates separate Go types per-query per-type, so I don't believe this would have any negative effects in cases where the nested type is optional in one parent type and non-optional in another.
This would also support the common response when this behaviour of Go is questioned as a design flaw, which is that the implementer of the methods on an interface should be responsible for handling nil pointers. Or, to say it another way, the getter says it can return a string given any pointer to
FindPersonPersonFavouriteFood
, but currently if that pointer is nil it cannot.Of course, there's the fairly big caveat that genqclient hasn't said it implements any interfaces; I have 😁 But I believe such a use case was the intent behind the feature?
Describe alternatives you've considered
I appreciate that there are several reasons the maintainers may not want to implement this, for example:
== nil
Nevertheless, because it's perfectly valid in Go to call a method on a (typed) nil pointer, I think my suggestion is overall more type-safe if the getters are pointer receivers rather than value receivers.
Additional context
While Go is not my first language, I would be happy to look into implementing this change if there is any interest in it!
The text was updated successfully, but these errors were encountered: