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

refactor(context): refactor Keys type to map[any]any #3963

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

flc1125
Copy link
Contributor

@flc1125 flc1125 commented May 10, 2024

  • ⚠️ Destructive adjustments require evaluation.
  • Enable Keys to support more types.
package main

import "github.com/gin-gonic/gin"

func main() {
	var ctx *gin.Context
	type key struct{}
	
	
	ctx.Set(111, "111")
	ctx.Get(111)
	ctx.Get(key{})
	ctx.Get("111")
	// ……
}

The first step in solving #1123 (comment)

Signed-off-by: Flc゛ <four_leaf_clover@foxmail.com>
@flc1125 flc1125 changed the title refactor(context): refactor Keys to map[any]any refactor(context): refactor Keys type to map[any]any May 10, 2024
Copy link

codecov bot commented May 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.05%. Comparing base (3dc1cd6) to head (0336111).
Report is 59 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3963      +/-   ##
==========================================
- Coverage   99.21%   99.05%   -0.16%     
==========================================
  Files          42       44       +2     
  Lines        3182     2751     -431     
==========================================
- Hits         3157     2725     -432     
+ Misses         17       15       -2     
- Partials        8       11       +3     
Flag Coverage Δ
?
-tags "sonic avx" 99.04% <100.00%> (?)
-tags go_json 99.04% <100.00%> (?)
-tags nomsgpack 99.03% <100.00%> (?)
go-1.18 ?
go-1.19 ?
go-1.20 ?
go-1.21 99.05% <100.00%> (-0.16%) ⬇️
go-1.22 99.05% <100.00%> (?)
macos-latest 99.05% <100.00%> (-0.16%) ⬇️
ubuntu-latest 99.05% <100.00%> (-0.16%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jarrodhroberson
Copy link

Why not just make it comparable, or just any?

@flc1125
Copy link
Contributor Author

flc1125 commented May 14, 2024

Why not just make it comparable, or just any?

I think it's just like the key in context.WithValue(ctx, key, value).

It can be supported, so why not?

Moreover, when the key is set to struct{}, it saves more memory, ex:

type customKey struct{}

ctx.Set(customKey{}, "xxx")

package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var a int
	var b string
	var c struct{}

	fmt.Println("a:", unsafe.Sizeof(a))
	fmt.Println("b:", unsafe.Sizeof(b))
	fmt.Println("c:", unsafe.Sizeof(c))
}

output:

a: 8
b: 16
c: 0

@jarrodhroberson
Copy link

jarrodhroberson commented May 20, 2024 via email

@flc1125
Copy link
Contributor Author

flc1125 commented May 20, 2024

In Golang, maps can only have keys of types that are considered comparable.

This means the type needs to support the == and != operators for comparison.

which means that structs have to have nothing but comparable types as

their fields.

it introduces confusion when someone tries to use a key that is not

comparable.

*comparable *is broken as far as I am concerned, not being able to provide

a function that makes any struct comparable is a serious oversight and

flaw with the language.

I have ended up having to write a library that generates a unique hash of

any struct so that I can pass an array of structs I want to use as keys and

array of values and generate a map with consistent hashing of the identity

(fingerprint) of a struct as a key and the struct as the value. This allows

me to generate keys from structs and look them up in the map without having

to worry about if something I want to store in a map key is a valid key or

not.

On Tue, May 14, 2024 at 11:24 AM Flc゛ @.***> wrote:

Why not just make it comparable, or just any?

I think it's just like the key in context.WithValue(ctx, key, value).

It can be supported, so why not?

Moreover, when the key is set to struct{}, it saves more memory, ex:

type customKey struct{}

ctx.Set(customKey{}, "xxx")

Reply to this email directly, view it on GitHub

#3963 (comment), or

unsubscribe

https://github.com/notifications/unsubscribe-auth/AABF7754OZ67JDDN5LWVVBDZCIUERAVCNFSM6AAAAABHQT7FSWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMJQGUZDINBYHA

.

You are receiving this because you commented.Message ID:

@.***>

--

Jarrod Roberson

678.551.2852

Great reminder, I will add some single test trials.

@flc1125
Copy link
Contributor Author

flc1125 commented May 21, 2024

@jarrodhroberson It is indeed recommended to change to map[comparable]any for better practice, but unfortunately, Go does not support this format. After checking the official documentation, no better solution has been found. Therefore, I have maintained the map[any]any format and added some unit test scripts.

https://github.com/gin-gonic/gin/pull/3963/files#diff-e6ce689a25eaef174c2dd51fe869fabbe04a6c6afbd416b23eda138c82e761baR220-R247

Some references: golang/go#51384

c.Set([]int{1}, 1)
c.Set(func() {}, 1)
c.Set(make(chan int), 1)
})
}

func TestContextSetGetValues(t *testing.T) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this test also check that it's possible to retrieve pointer by key? I know that it's not part of the scope of your task changes but I see that it's missing from here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is your meaning nil?

@@ -217,6 +217,34 @@ func TestContextSetGet(t *testing.T) {

assert.Equal(t, "bar", c.MustGet("foo"))
assert.Panics(t, func() { c.MustGet("no_exist") })

// other types
type key struct{}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth extracting into

func TestContextSetGetAnyKey(t *testing.T)

}

// no comparable
assert.Panics(t, func() {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth extracting into

func TestContextSetGetPanicsWhenKeyNotComparable(t *testing.T)

@bound2
Copy link

bound2 commented May 24, 2024

@jarrodhroberson It is indeed recommended to change to map[comparable]any for better practice, but unfortunately, Go does not support this format. After checking the official documentation, no better solution has been found. Therefore, I have maintained the map[any]any format and added some unit test scripts.

https://github.com/gin-gonic/gin/pull/3963/files#diff-e6ce689a25eaef174c2dd51fe869fabbe04a6c6afbd416b23eda138c82e761baR220-R247

Some references: golang/go#51384

Yep went over source and there's no exposed func that could be turned into interface type with the given signature. Closest thing was cmp package but builtin types don't conform to its Equal.

@jarrodhroberson
Copy link

@jarrodhroberson It is indeed recommended to change to map[comparable]any for better practice, but unfortunately, Go does not support this format. After checking the official documentation, no better solution has been found. Therefore, I have maintained the map[any]any format and added some unit test scripts.
https://github.com/gin-gonic/gin/pull/3963/files#diff-e6ce689a25eaef174c2dd51fe869fabbe04a6c6afbd416b23eda138c82e761baR220-R247
Some references: golang/go#51384

Yep went over source and there's no exposed func that could be turned into interface type with the given signature. Closest thing was cmp package but builtin types don't conform to its Equal.

That is one of the inherit flaws of Go in reaction to all the insanely stupid things that other languages (JS cough cough) allow you to do. The inability to define an comparable function for your own types is one of them. Thanks for all the work on trying to so some kind of qualify life enhancements. If the Gin team would just make this an Interface it would make all this irrelevant.

@xiaotushaoxia
Copy link

xiaotushaoxia commented Jul 2, 2024

break backwards. looks not good for me.
if somebody write code like this, this pr make it build fail

func handleTest(c *gin.Context)  {
	var cpKeys = make(map[string]any, len(c.Keys))
	for s, a := range c.Keys {
		cpKeys[s] = a
	}
	// ....
}

@dmitry-novozhilov
Copy link

if somebody write code like this, this pr make it build fail

But gin.Context is an implementation of context.Context.

And it is a broken implementation, because keys should be any, like in the original context.Context.

And so this change is more important, because it fixes this bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants