How does type reflection for implicit types work? - go

From what I've understood, Go is statically typed and doesn't normally do implicit type conversions. So constants declared without an explicit type get based on what's required when they are first used.
So in the following snippet, I'd expect n to be a float64, since that's what math.Sin expects. But when printing out the reflected type, I see an int.
package main
import (
"fmt"
"math"
"reflect"
)
func main() {
const n = 5000 // No explict type
// fmt.Println(reflect.TypeOf(n)) // this would print "int"
fmt.Println(math.Sin(n)) // math.Sin expects a float64
fmt.Println(reflect.TypeOf(n)) // print "int"
}
What's actually happening here? Does n actually have an implict int type? Or does reflection not show the actual type cases like this? I don't think math.Sin is typecasting it's argument since the compiler throws an error if I specify an explicit type.

[Untyped constants get typed] based on what's required when they are first used.
This is where you're misunderstanding. A type is selected for every use independently.
math.Sin requires a float64 argument, so the compiler must select float64 here.
reflect.TypeOf takes an interface{} argument, so the compiler is free to choose any of the numeric types (because they all implement the empty interface). Here it chose the default integer type: int.

Related

How to define two separate types for arbitrary precision decimals so they can only be be used with the same type and not assigned be to each other

My current code base defines two types like this:
type Price uint64
type Quantity uint64
This works out nicely as I can't accidentally pass a Price type into a Quantity or else the compiler will complain.
I now need to switch the implementation from uint64 to an arbitrary precision decimal using the shopspring/decimal library.
I'd like the following requirements that worked from the previous uint64 implementation:
If I pass a Price to a function expecting a Quantity and vice-versa, the compiler will complain
I can do calculations (such as calling Add) between two Quantity's without any extra boilerplate, but for doing calculations between different types (such as multiplying a Price by a Quantity), I need to explicitly allow it by doing something such as casting.
I'd like to not have duplicate code such as defining every single method I want to use separately for each type (even if it delegates to a common implementation)
I've tried 3 different implementations, but none of them work right. Is there any approach that I am missing that would do what I want? If not, what is the recommended way to do things?
Approach 1
type Price decimal.Decimal
type Quantity decimal.Decimal
This implementations means I can't use methods on decimal.Decimal (such as Add()) for variables of type Price since according to the Go spec "It does not inherit any methods bound to the given type".
Approach 2
I can use a type alias like this:
type Price = decimal.Decimal
type Quantity = decimal.Decimal
but in this case I can pass a Price into a function expecting a Quantity so I don't get the type protection. Some documentation says the type aliases are mainly for helping during refactoring.
Approach 3
I can try to use an embedded type:
type Quantity struct {
decimal.Decimal
}
This works in most cases, but in this case:
qty.Add(qty2)
qty2 isn't a decimal.Decimal so I'd have to do ugly things like
qty.Add(qty2.Decimal)
You can use this approach with generics. It's easier to do for a type you write yourself. If you want to implement it with an external type, you will need a wrapper.
Example:
type Number[K any] uint64
func (n Number[K]) Add(n2 Number[K]) Number[K] {
return n + n2
}
// These are dummy types used as parameters to differentiate Number types.
type (
price struct{}
quantity struct{}
)
func main() {
var somePrice Number[price]
var someQuantity Number[quantity]
// no error
somePrice.Add(somePrice)
// cannot use someQuantity (variable of type Number[quantity]) as type Number[price] in argument to somePrice.Add
somePrice.Add(someQuantity)
}
Now if you want to do this for an external type like decimal.Decimal, which you can't edit the source for to make it work like this, you must write wrappers for any methods where you need the parameter types to be covariant with the receiver type.
Example, here I'm assuming you're using the https://github.com/shopspring/decimal library:
package main
import "github.com/shopspring/decimal"
type Number[K any] struct{ decimal.Decimal }
// Wrapper to enforce covariant type among receiver, parameters and return.
func (n Number[K]) Add(d2 Number[K]) Number[K] {
return Number[K]{n.Decimal.Add(d2.Decimal)}
}
// These are dummy types used as parameters to differentiate Number types.
type (
price struct{}
quantity struct{}
)
func main() {
var somePrice Number[price]
var someQuantity Number[quantity]
// no error
somePrice.Add(somePrice)
// cannot use someQuantity (variable of type Number[quantity]) as type Number[price] in argument to somePrice.Add
somePrice.Add(someQuantity)
}
You will need a wrapper for each method with covariant types.
Alternatively, you can make your own library or fork the existing one and add this feature directly with the method in the first example:
For example, your fork of decimal.go could look like:
// +++++++
type Decimal[K any] struct { ... }
// +++ +++ +++
func (d Decimal[K]) Add(d2 Decimal[K]) Decimal[K] { ... }

Go type definition and initialize a pointer [duplicate]

I am using custom types and I have a problem when pointers are involved like below.
Code below is valid:
package main
import (
"fmt"
)
type deck []string
func newDeck(cards ...string) deck {
return cards
}
Code below is valid too:
package main
func str(n []string) *[]string {
return &n
}
The below code instead is not valid. Why so? I have to write a type conversion like return (*deck)(&cards)
package main
import (
"fmt"
)
type deck []string
func newDeck(cards ...string) *deck {
return &cards // compiles with return (*deck)(&cards)
}
The rules about assignments (including returns) are defined in the Go specs: Assignability. The one that is relevant to your case is:
V and T have identical underlying types and at least one of V or T is not a named type.
And Underlying types:
If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself.
The first example compiles because []string is an unnamed type literal with underlying type []string (itself) and deck is a named type with underlying type []string (by your type definition).
The second example does not compile because both *[]string and *deck are unnamed type literals with themselves as (different) underlying types.
To make the second example compile, you can't rely on a direct assignment, but, as you found out, use an explicit type conversion
return (*deck)(&cards)
And this conversion is valid due to the following rule:
ignoring struct tags (see below), x's type and T are pointer types that are not named types, and their pointer base types are not type parameters but have identical underlying types.

Go assignment involving pointers to custom types

I am using custom types and I have a problem when pointers are involved like below.
Code below is valid:
package main
import (
"fmt"
)
type deck []string
func newDeck(cards ...string) deck {
return cards
}
Code below is valid too:
package main
func str(n []string) *[]string {
return &n
}
The below code instead is not valid. Why so? I have to write a type conversion like return (*deck)(&cards)
package main
import (
"fmt"
)
type deck []string
func newDeck(cards ...string) *deck {
return &cards // compiles with return (*deck)(&cards)
}
The rules about assignments (including returns) are defined in the Go specs: Assignability. The one that is relevant to your case is:
V and T have identical underlying types and at least one of V or T is not a named type.
And Underlying types:
If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself.
The first example compiles because []string is an unnamed type literal with underlying type []string (itself) and deck is a named type with underlying type []string (by your type definition).
The second example does not compile because both *[]string and *deck are unnamed type literals with themselves as (different) underlying types.
To make the second example compile, you can't rely on a direct assignment, but, as you found out, use an explicit type conversion
return (*deck)(&cards)
And this conversion is valid due to the following rule:
ignoring struct tags (see below), x's type and T are pointer types that are not named types, and their pointer base types are not type parameters but have identical underlying types.

Is type casting structs in Go a no-op?

Consider the following code in Go
type A struct {
f int
}
type B struct {
f int `somepkg:"somevalue"`
}
func f() {
var b *B = (*B)(&A{1}) // <-- THIS
fmt.Printf("%#v\n", b)
}
Will the marked line result in a memory copy (which I would like to avoid as A has many fields attached to it) or will it be just a reinterpretation, similar to casting an int to an uint?
EDIT: I was concerned, whether the whole struct would have to be copied, similarly to converting a byte slice to a string. A pointer copy is therefore a no-op for me
It is called a conversion. The expression (&A{}) creates a pointer to an instance of type A, and (*B) converts that pointer to a *B. What's copied there is the pointer, not the struct. You can validate this using the following code:
a:=A{}
var b *B = (*B)(&a)
b.f=2
fmt.Printf("%#v\n", a)
Prints 2.
The crucial points to understand is that
First, unlike C, C++ and some other languages of their ilk, Go does not have type casting, it has type conversions.
In most, but not all, cases, type conversion changes the type but not the internal representation of a value.
Second, as to whether a type conversion "is a no-op", depends on how you define the fact of being a no-op.
If you are concerned with a memory copy being made, there are two cases:
Some type conversions are defined to drastically change the value's representation or to copy memory; for example:
Type-converting a value of type string to []rune would interpret the value as a UTF-8-encoded byte stream, decode each encoded Unicode code point and produce a freshly-allocated slice of decoded Unicode runes.
Type-converting a value of type string to []byte, and vice-versa, will clone the backing array underlying the value.
Other type-conversions are no-op in this sense but in order for them to be useful you'd need to either assign a type-converted value to some variable or to pass it as an argument to a function call or send to a channel etc — in other words, you have to store the result or otherwise make use of it.
All of such operations do copy the value, even though it does not "look" like this; consider:
package main
import (
"fmt"
)
type A struct {
X int
}
type B struct {
X int
}
func (b B) Whatever() {
fmt.Println(b.X)
}
func main() {
a := A{X: 42}
B(a).Whatever()
b := B(a)
b.Whatever()
}
Here, the first type conversion in main does not look like a memory copy, but the resulting value will serve as a receiver in the call to B.Whatever and will be physically copied there.
The second type conversion stores the result in a variable (and then copies it again when a method is called).
Reasonong about such things is easy in Go as there everything, always, is passed by value (and pointers are values, too).
It may worth adding that variables in Go does not store the type of the value they hold, so a type conversion cannot mutate the type of a variable "in place". Values do not have type information stored in them, either. This basically means that type conversions is what compiler is concerned with: it knows the types of all the participating values and variables and performs type checking.

Go: Type assertions on structs?

package main
import "fmt"
type Number int
func (n *Number) IncreaseMe(i int) {
*n += i
}
func main() {
n := Number(10)
n.IncreaseMe(90) // n is now supposed to be 100
fmt.Println(n)
}
When running the code above, it gives me the error message
invalid operation: *n += i (mismatched types Number and int)
Which is to be expected as it's trying to do a math operation on variables which don't share the same type.
I then tried
*n.(int) += i
which tells the compiler not to worry as *n can be safely treated as an integer, which leads me to
invalid type assertion: n.(int) (non-interface type *Number on left)
I believe this is happening because type assertions works only with interfaces, not custom types.
So what is the solution for this?
As mentioned in "Go: Named type assertions and conversions"
Type assertion works for interfaces only. Interface can have arbitrary underlying type, so we have type assertion and type switch to the rescue.
You don't need type assertion: you can just:
convert *n to an int: int(*n) (since you know full well the actual type).
make the addition
convert back to Number
*n = Number(int(*n) + i)
See this play.golang.org: the output is 100.
As ruakh mentions in the comments:
Go splits the concept of "casting" into two separate concepts:
one ("type conversions") for conversions that the compiler knows are correct, and
one ("type assertions") for conversions that must be checked at runtime.
The latter only applies to interfaces, since only interfaces have additional type information at runtime that that's not known at compile time.

Resources