GithubHelp home page GithubHelp logo

Comments (6)

dsnet avatar dsnet commented on August 19, 2024 1

Both of those are function pointers. Functions in Go are not comparable (as defined in the language spec). In this situation, cmp.Equal took the same definition as reflect.DeepEqual with regard to function pointers where they are only equal if both are nil, otherwise they are never equal.

Even though both appear to have the same pointer, that is actually an implementation detail of the current Go compiler. For top-level functions, a pointer has a well-defined meaning. However, when you derive a function pointer from a method or closure, the meaning becomes more hazzy.

func MakeClosure() func() int {
    var x int
    return func() {
        x++
        return x
    }
}

f1 := MakeClosure()
f2 := MakeClosure()
f1 == f2 // Suppose this were possible, what should this return?

from go-cmp.

dsnet avatar dsnet commented on August 19, 2024 1

That seems highly suspect. See golang/go#18871.

Comparing the reflect.Value directly also compares the reflect.flags type, which is entirely an implementation detail of the "reflect" package.

from go-cmp.

dsnet avatar dsnet commented on August 19, 2024

This is behaving as intended. reflect.Type is a good example of a type that should not be compared by deeply descending into it since it contains state that it not safe to compare.

The user should be explicit about the proper way to compare reflect.Type:

cmp.Comparer(func(x, y reflect.Type) bool { return x == y })

Sometimes pointer comparisons are the right approach, other times the right approach is to descend into the value. reflect.DeepEqual is inconsistent in that it only does pointer comparison if they are identical, otherwise it descends. cmp.Equal chose to always descends into pointers.

We chose the current behavior for the following reasons:

  1. Always descending is more expressive, since you could always "opt-out" of it by providing a custom Comparer to do pointer comparison. The opposite is not true.
  2. There exists comparisons in Go that are irreflexive (e.g., NaN != NaN or comparisons on function pointers). Thus, performing pointer comparison hides the fact that the underlying values may actually be unequal by other rules of comparison (see this example). Personally, I'm not fond of irreflexive equalities, but they exist and I prefer to keep the number of exceptions lower.

from go-cmp.

dsnet avatar dsnet commented on August 19, 2024

Here's an example of how an option can be used to obtain the equivalent behavior of reflect.DeepEqual with regard to pointers:

func EquateIdenticalPointers() cmp.Option {
	return cmp.FilterPath(func(p cmp.Path) bool {
		// Filter for pointer kinds only.
		t := p.Last().Type()
		return t != nil && t.Kind() == reflect.Ptr
	}, cmp.FilterValues(func(x, y interface{}) bool {
		// Filter for pointer values that are identical.
		vx := reflect.ValueOf(x)
		vy := reflect.ValueOf(y)
		return vx.IsValid() && vy.IsValid() && vx.Pointer() == vy.Pointer()
	}, cmp.Comparer(func(_, _ interface{}) bool {
		// Consider them equal no matter what.
		return true
	})))
}

Note that this option will conflict with another option that is applicable on *T. See #36 for a proposed solution around that. With #36, you could always wrap inside testify as such:

return cmp.Diff(x, y,
	DeepAllowUnexported(x ,y),
	cmp.FilterOrder(
		cmp.Options(userOpts), // User-provided options take precedence
		EquateIdenticalPointers(),
	),
)

from go-cmp.

posener avatar posener commented on August 19, 2024

I see
Thank you very much for the detailed explanation.
Make sense all that you said here.

One last thing that is not very clear to me.
Can you explain why the comparison failed? the diff looks like they are identical:

{*reflect.rtype}.alg.hash:
	-: (func(unsafe.Pointer, uintptr) uintptr)(0x451530)
	+: (func(unsafe.Pointer, uintptr) uintptr)(0x451530)
{*reflect.rtype}.alg.equal:
	-: (func(unsafe.Pointer, unsafe.Pointer) bool)(0x401a50)
	+: (func(unsafe.Pointer, unsafe.Pointer) bool)(0x401a50)

from go-cmp.

prochac avatar prochac commented on August 19, 2024

@dsnet
It is what @matryer is doing here, right?
https://github.com/matryer/is/blob/master/is.go#L257

The simple function pointer works, closure is not.
It kinda makes sense, as it does have different stack bound to it.
https://go.dev/play/p/IcBdFTMhrgT

from go-cmp.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.