As of 2021-11-17, there is probably no cache library that uses the Go 1.18 Generics feature.
I've tried to implement the first Go 1.18 generics cache library here. I'm very happy if you give me a GitHub star.
https://github.com/Code-Hex/go-generics-cache
In this article, I'll introduce some of the things I noticed about Go Generics while developing this cache library, as well as some of the tips and bothers I found.
Return zero value for any type
You will often write code that returns any and error, such as the following. When an error occurs in a function, you would have written code that returns zero-value and error, but now you need to think a little bit differently.
funcDo[Vany](vV)(V,error){iferr:=validate(v);err!=nil{// What should we return here?}returnv,nil}funcvalidate[Vany](vV)error
Let’s suppose you writereturn 0, err
here. This will be a compilation error. The reason is thatany
type can be a type other thanint
type, such asstring
type. So how do we do this?
Let's declare a variable once usingV
of the type parameter. Then you can write it in a compilable form as follows.
funcDo[Vany](vV)(V,error){varretViferr:=validate(v);err!=nil{returnret,err}returnv,nil}
In addition, named return values can be used to simplify the writing for a single line.
funcDo[Vany](vV)(retV,_error){iferr:=validate(v);err!=nil{returnret,err}returnv,nil}
https://gotipplay.golang.org/p/0UqA0PIO9X8
Don't try to do type switch withconstraints
I wanted to provide two methods,Increment
andDecrement
. They can add or subtract values from thego-generics-cache library if the stored value satisfies theNumber constraint.
Let's useIncrement
method as an example. I initially wrote code like this.
typeCache[Kcomparable,Vany]struct{itemsmap[K]V}func(c*Cache[K,V])Increment(kK,nV)(valV,_error){got,ok:=c.items[k]if!ok{returnval,errors.New("not found")}switch(interface{})(n).(type){caseNumber:nv:=got+nc.items[k]=nvreturnnv,nil}returnval,nil}
I was thinking of using the type of the valuen V
to match the constraints that are satisfied. This method that adds if theNumber
constraint is satisfied, and does nothing otherwise.
This will not compile.
- Go does not provide conditional branching for constraints.
- constraints is an interface. Go does not allow type assertions using interface.
- The type of
n
is not determined, so+
operation is not possible. - In the first place, there is no guarantee that
items
type is the same type asn
.
To solve this problem, I redesigned the interface. Why did I want to create methods in theCache
struct?
- To inherit the data of the fields held by the
Cache
struct. - To handle methods of the
Cache
.
To solve these points, I decided to embed theCache
struct. And I defined aNumberCache
struct that can always handleNumber
constraints.
typeNumberCache[Kcomparable,VNumber]struct{*Cache[K,V]}
This way, we can guarantee that the type of the value passed to theCache
struct will always be aNumber
constraint. So we can add anIncrement
method toNumberCache
struct.
func(c*NumberCache[K,V])Increment(kK,nV)(valV,_error){got,ok:=c.Cache.items[k]if!ok{returnval,errors.New("not found")}nv:=got+nc.Cache.items[k]=nvreturnval,nil}
https://gotipplay.golang.org/p/poQeWw4UE_L
The point of bothered me
Let's look at the definition of theCache
struct again.
typeCache[Kcomparable,Vany]struct{itemsmap[K]V}
Go Generics is defined as a language specification with a constraint which is calledcomparable
. Which allows only types can use==
and!=
.
I feel that this constraint is bothered me. Let’s explain the reasons why bother me.
I defined a function that compares twocomparable
values.
funcEqual[Tcomparable](v1,v2T)bool{returnv1==v2}
Allowing onlycomparable
types are going to result in an error if an incomparable type is passed to the function at compile-time. You may think this is useful.
However, according to Go's specification,interface{}
also satisfies thiscomparable
constraint.
Ifinterface{}
can be satisfied, the following code can be compiled.
funcmain(){v1:=interface{}(func(){})v2:=interface{}(func(){})Equal(v1,v2)}
This shows thatfunc()
type which is a non-comparable type. but can be converting as a comparable type by casting it to theinterface{}
type.
interface{}
type will only know at runtime whether it is a comparable type or not.
If this is a complex code, it may be difficult to notice.
https://gotipplay.golang.org/p/tbKKuehbzUv
I believe that we need another comparable constraints that do not acceptinterface{}
to notice at compile-time.
Can this constraints be defined by Go users? The answer is currently not.
This is becausecomparable
constraint contains "comparable structures" and "comparable arrays". These constraints cannot currently be defined by Go users. Therefore, I would like to provide them as a Go specification.
I also created a proposal for it, so if you can relate to it, I would appreciate it if you could give me 👍 in GitHub issue.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse