-
Notifications
You must be signed in to change notification settings - Fork 16
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 types #56
base: master
Are you sure you want to change the base?
Refactor types #56
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Equals method issue can be resolved, by changing the signature to Equals(o any) bool
I don't think this would be any useful imo, you can always cast to the primitive type and do a normal ==
comparison
To get around the missing List and Map element types, we can use reflect for this
IIRC using reflect will not initialize the internal pointers of the struct, so I believe using type aliases for List and Map would not be feasible (and it would require more rebasing anyway).
Not in types which take in an Also it would allow for code like However it WOULD allow for us to use
In my testing it did, but this can be done. I'll play around with it tomorrow unless you beat me to it |
I just tested this and it does create a new pointer to the struct: package main
import (
"fmt"
"reflect"
)
type RVType interface {
Extract()
Copy() RVType
}
type Struct struct {
field string
}
func (s *Struct) Extract() {}
func (s Struct) Copy() RVType {
return &Struct{field: s.field}
}
type List[T RVType] struct {
real []T
}
func (l List[T]) newType() T {
var t T
tType := reflect.TypeOf(t).Elem()
return reflect.New(tType).Interface().(T)
}
func (l List[T]) test() {
t := l.newType()
fmt.Printf("%T\n", t)
}
func main() {
list := List[*Struct]{
real: make([]*Struct, 0),
}
list.test() // *main.Struct
} In this example |
This is why I proposed adding a new method such as
package main
import (
"fmt"
)
type RVType interface {
ExtractFrom()
}
type Struct struct {
field string
}
func (s *Struct) ExtractFrom() {
s.field = "test"
}
func main() {
s := Struct{}
s.ExtractFrom()
fmt.Printf("%+v\n", s) // {field:test}
} That being said, even if it did:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have PRs on nex-protocols-go
and nex-protocols-common-go
ready before merging this, so that this doesn't block any new changes there
types/any_data_holder.go
Outdated
copied.TypeName = adh.TypeName.Copy().(String) | ||
copied.Length1 = adh.Length1.Copy().(UInt32) | ||
copied.Length2 = adh.Length2.Copy().(UInt32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since structures don't hold pointers anymore, can't we use direct assignment on here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Copy
method must return an RVType
to conform to the interface, so we cannot make them return more specific types. Trying to remove the assertion here throws the error cannot use adh.TypeName.Copy() (value of type RVType) as String value in assignment: need type assertion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant like:
copied.TypeName = adh.TypeName
since both copied
and adh
are the same type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, that does work. We can use that in all cases where the fields aren't pointers/don't have pointer fields
The previous method didn't work because it was making a pointer of the RVType interface instead of the underlying data.
Addresses concerns mentioned in #50
Changes:
Makes large changes to how we handle types, both in terms of the API and internally. In #42 we made Go's built in types into our own types, but all the types we made were structs. This includes simple types, like the built in ones, which only had a single field to hold the "real" value. This was caused by a misunderstanding of the type system and how pointer receivers worked with interfaces/generics, deeming type aliases nonviable.
This was incorrect, type aliases ARE viable. PR makes the following changes:
Primitive
types to just their type names. AddingPrimitive
was both a mouthful and unnecessaryList
andMap
into type aliasesMap
KEYS MUST NOW IMPLEMENT THEcomparable
TYPE CONSTRAINT. THIS MEANSMap
ANDList
THEMSELVES CANNOT BE KEYS, NOR CAN *ANY* TYPE WHICH HAS THOSE AS FIELDS ANYWHERE IN ITS TREEStationURL
to be more accurately implemented in terms of the APIStationURL
to prevent badly formatted URLs from reaching clientsStationURL
. This is not used by NEX, but is used by Rendez-Vous titles like WATCH_DOGSNew
methods for types no longer return pointers by defaultensureFields
method existing on complex types that may need to ensure some fields are initialized before use. This is required for using complex types inMap
andList
typesThis allows for a more streamlined way of interacting with these types, allowing for code such as:
Where our custom types can be used more-or-less like built in types, rather than using an arbitrary
Value
field or method, and needing to export many methods for basic operations like bitwise on our numeric types.This PR DOES NOT:
Equals
methods to accept anything other than a pointer to the same typeMake theIt does nowList
type into a type aliasMake theIt does nowMap
type into a type aliasThe
Equals
method issue can be resolved, by changing the signature toEquals(o any) bool
, and then checking for more types. This just requires some extra checks, and I'm not sure how useful it would be? With type aliases on simple types we can just use the==
operator now, rather thanEquals
(which is now really only useful when you JUST have anRVType
and don't know its type?)Edit:
Map
andList
are now type aliases as wellThe `List` and `Map` types not being type aliases also means that the original issues described in #50 are still relevant. However these CAN viably be made into type aliases, but come with some caveats which are going to require more changes across the other libraries.
- We lose access to the
- The element type MUST be a pointer, not the raw value. We already do this, but it's important to keep in mind
- We lose access to the key and value type fields. Which means we can no longer use it to create new instances of the
- The
- The
- A good amount of work to refactor the types
- No longer safe to assume we always have a pointer to the type’s data, so we need to make sure we are going to and from pointers correctly (compiler should catch this, but could make for some possibly uglier code?)
- Possibility for larger memory usage. If we stop passing pointers around, then large structs COULD take up more memory in some cases. However to be fair, none of our structs are very large and are used in pretty one-off situations with little (if any?) pointer sharing anyway. So it likely won't have a HUGE impact on memory usage
The
List
type can somewhat easily be turned into a type alias like so:However this has 2 caveats:
Type
field. Which means we can no longer use it to create new instances of theList
element type. However there is a way around this, which is mentioned laterThe
Map
type is a bit different. To make it into a type alias, we have to use themap
basic type. This type REQUIRES that the key type implements thecomparable
constraint. What this means is that the type can be evaluated with the==
operator, which isn't always guaranteed for custom types depending on how they're implementedGiven that, the
Map
type can also be made into a type alias like so:However this has 3 caveats:
Map
element types. However there is a way around this, which is mentioned laterMap
values MUST be a pointer, not the raw value. We already do this, but it's important to keep in mindMap
key MUST be able to safely be evaluated with the==
operator, which currently NONE of our struct-based types are. Our type alias types ARE, so types such asUInt32
can safely be used, but ONLY when NOT used as pointers. SoMap[UInt32, *Something]
would work, butMap[*UInt32, *Something]
would NOT work.We CAN get around the 3rd issue, however. Struct-based types WILL evaluate safely with
==
IF they contain the same data:However to do this it means that NONE of the structs fields can be pointers, they MUST be whole values. Which means we would need to completely change how we manage types in https://github.com/PretendoNetwork/nex-protocols-go as well as everywhere else. This IS viable, but comes with the following caveats:
Making the
List
andMap
types would potentially address/fix the issues mentioned in #50, but would need more of a rework in other libraries.To get around the missing
List
andMap
element types, we can usereflect
for this. In the past we have avoided reflection when possible as it can be slow and error prone. However I ran several benchmarks against usingreflect
with a test implementation ofList
and found no noticeable difference in performance. The code for this would look something like: