Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node/bindnode: allow nilable types instead of a pointer for optional/nullable schemas #378

Closed
mvdan opened this issue Mar 7, 2022 · 4 comments · Fixed by #401
Closed
Assignees

Comments

@mvdan
Copy link
Contributor

mvdan commented Mar 7, 2022

For example, right now, to bind an IPLD struct { Foo optional Any }, one must use a pointer for the field type, like struct { Foo *datamodel.Node }.

However, in this case and some others, the Go type datamodel.Node already has a nil state to signal "absent", so we could reuse that instead of adding our own via a pointer. Not only is that type easier for the user to read and write, but it's also less prone to bugs, because a pointer to an interface has two nil states: nil pointer, and non-nil pointer to a nil interface.

This would also apply to IPLD schema's nullable, which similarly requires a pointer now.

This would have helped with #371, and also with the new #377.

What to do with optional nullable fields remains a bit of a TODO, because right now those use two chained pointers, which is a bit weird. We might want to rethink that with generics at some point, to end up with a wrapper like OptionalNullable that keeps two boolean fields, akin to #340.

@rvagg
Copy link
Member

rvagg commented Mar 8, 2022

Thinking aloud: I wonder if optional nullable needs an additional specifier to dictate the default value, like implicit: Foo optional nullable Any (implicit "null"), or maybe something dedicated like (defaultnull) - such that bindnode could use this as a hint so that when serializing a nil field it'll know to put in a Null rather than leave it out. You'd still not get the ability to serialize an absent, but maybe that won't matter in most cases if you can control it via schema and allow deserialization to just work?

mvdan added a commit that referenced this issue Mar 8, 2022
The implementation expected map value types in Go to be a pointer
when the IPLD schema has the value as nullable.
Unfortunately, we forgot to enforce that in verifyCompatibility,
so the user could run into weird panics:

	panic: interface conversion: basicnode.plainList is not datamodel.Node: missing method Length

The added test case in TestPrototypePointerCombinations reproduces the
panic, but is commented out, because we're fixing the check logic to
instead have bindnode panic upfront with a helpful message.

Note that I understand why the user tried to avoid the pointer to Node.
Since Node is an interface, it is already nilable, so bindnode can store
a nil value in it directly without the need for a pointer.
We do not support that right now, but soon will; #378 now tracks that.

Fixes #377.
@mvdan
Copy link
Contributor Author

mvdan commented Mar 22, 2022

@rvagg that seems like a question for the schema language more than for bindnode :) My interpretation of optional nullable is that the zero or implicit state is absent, not null, and I think that goes in line with codegen.

That said, bindnode still deals with optional nullable in a bit of a weird way, with two layers of pointers. I've added a comment to #340 to expand on that a little when it comes to generic wrappers.

@BigLep
Copy link

BigLep commented Apr 5, 2022

@mvdan : are you going to be able to finish landing this before leaving?

@mvdan
Copy link
Contributor Author

mvdan commented Apr 7, 2022

@BigLep I have a work-in-progress patch that I intend to send by the end of the week; it might not land this week, but since this is open source, I'm not too worried about it leaking into next week.

rvagg pushed a commit that referenced this issue Apr 26, 2022
For simplicity, bindnode used to always require a pointer to represent
optional or nullable IPLD types. This is because we need an extra bit to
store whether the value is absent or null.

However, some Go types can already store a "nil" value to represent that
bit without needing that extra pointer. Most notably:

	[]T            versus *[]T
	map[K]V        versus *map[K]V
	datamodel.Node versus *datamodel.Node

Avoiding the extra pointer makes the types easier for humans to deal
with, and it also avoids a potential footgun due to the extra "nil"
state that bindnode doesn't actually use.

Note that we still require pointers for "optional nullable" struct
fields, as those need two extra bits. A TODO is left for that edge case.

Fixes #378.
rvagg pushed a commit that referenced this issue Apr 27, 2022
For simplicity, bindnode used to always require a pointer to represent
optional or nullable IPLD types. This is because we need an extra bit to
store whether the value is absent or null.

However, some Go types can already store a "nil" value to represent that
bit without needing that extra pointer. Most notably:

	[]T            versus *[]T
	map[K]V        versus *map[K]V
	datamodel.Node versus *datamodel.Node

Avoiding the extra pointer makes the types easier for humans to deal
with, and it also avoids a potential footgun due to the extra "nil"
state that bindnode doesn't actually use.

Note that we still require pointers for "optional nullable" struct
fields, as those need two extra bits. A TODO is left for that edge case.

Fixes #378.
masih added a commit to ipni/storetheindex that referenced this issue Jul 14, 2022
Earlier versions of bindnode required pointers in order to represent
optional/nullable fields even if the go type was nillable.

Now that bindnode allows nillable types to represent optional/nulalble
fields, remove pointer to interfaces and simply use the interface type
straight up.

Relates to:
 - ipld/go-ipld-prime#378
masih added a commit to ipni/storetheindex that referenced this issue Jul 14, 2022
Earlier versions of bindnode required pointers in order to represent
optional/nullable fields even if the go type was nillable.

Now that bindnode allows nillable types to represent optional/nulalble
fields, remove pointer to interfaces and simply use the interface type
straight up.

Relates to:
 - ipld/go-ipld-prime#378
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 🎉 Done
Development

Successfully merging a pull request may close this issue.

3 participants