Confused? type definition

I often see codes which define custom type with underlying type.
However there seem to be some confused behaviors.

  • string can be assign as Method without cast
  • string can be used for function argument as Method without cast
  • string cannot become receiver for Method functions
  • Method cannot be assign as string without cast
  • Method can be used for function argument as string without cast

This can be regarded as consistent, like the relation between super class and subclass in OOP(but string is subclass from implicit cast point of view :thinking:interesting.) but in the example below you may
incorrectly use B and C as Method unless defining receiver methods.

Changing behaviors will lose backward compatibility so there is no need to do anything for Go codes, in my opinion.

But this could cause bugs in some case, for example you write codes which depends on reflect.TypeOf or type cast.

Do you have any ideas to reduce these risks and misunderstandings?

https://play.golang.org/p/G7EosLYLGZj

type Method string

const (
	A Method = "A" // A is Method
	B        = "B" // B is string
	C        = "C" // C is string
)

func (m Method) String() string {
	return string(m)
}

func ToMethod(m string) Method {
	switch m {
	case "A":
		return A
	case "B":
		return B
	case "C":
		return C
	default:
		return A
	}
}

func ToString(m Method) string {
	return string(m)
}

func AsString(m string) string {
	return m
}

type MethodConfig struct {
	method Method
        methodStr string
}

func main() {
	fmt.Println(A.String())
	//fmt.Println(B.String())
	//fmt.Println(C.String())
	fmt.Println(reflect.TypeOf(A))
	fmt.Println(reflect.TypeOf(B))
	fmt.Println(reflect.TypeOf(C))

	a := ToMethod("A")
	b := ToMethod("B")
	c := ToMethod("C")
	fmt.Println(a.String())
	fmt.Println(b.String())
	fmt.Println(c.String())
	fmt.Println(reflect.TypeOf(a))
	fmt.Println(reflect.TypeOf(b))
	fmt.Println(reflect.TypeOf(c))

	ToString(A)
	ToString(B)
	ToString(C)

	//AsString(A)
	AsString(B)
	AsString(C)

	cfgA := MethodConfig{method: A}
	cfgB := MethodConfig{method: B, methodStr: B}
	cfgC := MethodConfig{method: C, methodStr: C}
	fmt.Println(cfgA.method.String())
	fmt.Println(cfgB.method.String())
	fmt.Println(cfgC.method.String())
	fmt.Println(reflect.TypeOf(cfgA.method))
	fmt.Println(reflect.TypeOf(cfgB.method))
	fmt.Println(reflect.TypeOf(cfgC.method))
}

Of course, the example below works consistently👍
(The misunderstanding may be caused by int iota sample😅)

type TimeZone int

const (
	EST TimeZone = -(5 + iota)
	CST
	MST
	PST
)

func (tz TimeZone) String() string {
	return fmt.Sprintf("GMT%+dh", tz)
}

Go has untyped constants, which (like literals) are assignable to variables of types with the same underlying type. This is so that you can say things like

var m Method = "hello"

and

var d = 5 * time.Millisecond

without running into an error because "hello" is not of type Method nor 5 of type time.Duration. The same assignability rule governs what you can pass as a function argument.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.