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

reflect: assignment compatibility doesn't include nil #6231

Closed
rogpeppe opened this issue Aug 23, 2013 · 7 comments
Closed

reflect: assignment compatibility doesn't include nil #6231

rogpeppe opened this issue Aug 23, 2013 · 7 comments

Comments

@rogpeppe
Copy link
Contributor

A couple of times recently I've come up against an awkwardness when using the reflect
package to pass an arbitrary value to a destination of the same type as the value.

The code that I naively wrote initially ran along the following lines:

    // Set sets the contents of dest, which must be a pointer to some type,
    // say *T, to the given value, which must be assignable to type T.
    func Set(dest, value interface{}) {
          reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(value))
    }

This at first seems to work fine (http://play.golang.org/p/OSz3NGEykB) but it fails when
value is nil (http://play.golang.org/p/0zPsQUiD3F)

To get this right, you have to do something like this:
http://play.golang.org/p/E_Rrhnokz_

I think it would be reasonable to make the original code work as expected; that is: we
could make it possible for a zero ("invalid") reflect.Value to be treated as
untyped nil for assignability purposes.

One suggestion is to change Value.Convert to allow a zero Value - I think this is
necessary but not sufficient, as for many purposes, assignability is exactly what we
want, and the stronger Covert operation will allow more types than desired.
@robpike
Copy link
Contributor

robpike commented Aug 24, 2013

Comment 1:

This could be a subtle and invasive change, so I doubt it will happen for Go 1.2. But I
agree the workaround is ugly.

Labels changed: added priority-later, feature, removed priority-triage, go1.2maybe.

@robpike
Copy link
Contributor

robpike commented Aug 24, 2013

Comment 2:

Status changed to Accepted.

@rsc
Copy link
Contributor

rsc commented Sep 3, 2013

Comment 3:

I don't believe this is a good idea. Yes, reflect can be chatty. But trying to add
syntactic sugar will make it even more confusing than it already is. There are other
ways to get the zero reflect.Value. Not all of them should be "nil" in this case.

@rogpeppe
Copy link
Contributor Author

Comment 4:

The basic problem is that the zero value is used in two different ways - as a genuinely
invalid value (as returned by Indirect, Elem, FieldByName, FieldByNameFunc, MapIndex,
MethodByName and TryRecv), and as the result of reflect.ValueOf(nil).
I would be happy if reflect.ValueOf(nil).IsValid() was true, but that's not a change we
can make.
I don't think it's too much of a problem that the zero value returned by the above calls
becomes assignment-compatible with nilable values (and it's still unambiguous in all
those cases, as a valid value returned by those methods will always have a valid type,
AFAICS).
However SetMapIndex is problematic, as it uses IsValid to determine whether it should
delete the key from the map, whereas my proposal would suggest that it should assign a
nil value to the map.
I think that means that it's not viable, sadly.
I would still very much like to see some kind of better way of working around this
issue, however. I have so far encountered the issue in at least separate domains so far
(assignment, channel sends and function calls) and, experimentally, people do not see
any potential problem with the naive code even when specifically told that there's a
problem there.
Perhaps a method like this might help, as an adjunct to Convert:
// AsType returns the value v with the given type t.
// As a special case, if v is the zero Value and t
// is a type that may have a nil value, AsType returns
// the nil value of that type.
// If v is not assignable to type t, AsType panics.
func (v Value) AsType(t Type) Value
Alternatively:
// ValueOfType is the same as ValueOf except that the
// returned value will always have type t. If i is nil
// and values of type t may be nil, the returned value
// will be reflect.Zero(t); otherwise if i is not assignable
// to type t, ValueOfType will panic.
//
// This can be useful when converting an externally supplied value
// that will be assigned to some known type.
func ValidValueOf(i interface{}, t Type) reflect.Value
It's would still be awkward to use (particularly when using
Value.Call) but still better than the status quo, I think.

@rsc
Copy link
Contributor

rsc commented Sep 10, 2013

Comment 5:

It is a mistake to try to overload the zero Value.
The problem here is a bad interaction between ValueOf and interfaces. Because ValueOf
takes an interface{}, the static type of the argument is lost and cannot be recovered.
You are arguing that the result in this case should have type interface{}, but what if
the argument was a nil io.Reader?
Yes, the API has a flaw. But it's pretty fundamental to the use of the interface{} as an
argument.
It's better to leave the flaw than make things worse by adding patches.
In your case, it is easy to check for src == nil in your wrapper.
http://play.golang.org/p/FiPiyKtPmi

Status changed to WorkingAsIntended.

@rogpeppe
Copy link
Contributor Author

Comment 6:

"In your case, it is easy to check for src == nil in your wrapper.
http://play.golang.org/p/FiPiyKtPmi"
That's tempting, but doesn't quite get the effect I intended - you
can then pass nil for non-nilable types.
http://play.golang.org/p/kNOIMy7aqB
I'd like a straightforward way to do it properly.

@rsc
Copy link
Contributor

rsc commented Sep 10, 2013

Comment 7:

I'd like that too, but it's not going to happen unless we stop using
interface{} for "anything".

@golang golang locked and limited conversation to collaborators Jun 25, 2016
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants