I'm trying to make an abstract data type representing a positive number:
package m
type positiveNum int
func MakePositiveNum(i int) positiveNum {
if i < 1 { panic("non positive number") }
return positiveNum(i)
}
// some function that expects a positive number
func UsePositiveNum(s positiveNum) {}
Here are some example uses:
package main
import "m"
func main() {
pn := m.MakePositiveNum(123)
//i := 1; m.UsePositiveNum(i) // fails as expected because
// int is passed instead of positiveNum
//useInt(pn) // fails because trying to pass positiveNum instead of int
//pn = m.positiveNum(0) // fails as expected because the type is private
m.UsePositiveNum(pn)
}
func UseInt(int) {}
If you replace m.UsePositiveNum(pn)
with m.UsePositiveNum(0)
, it still compiles, bypassing the positive number typecheck. Why?
What is happening here is that 0
is an untyped constant. Such constants are covered by this rule about assignability:
A value
x
is assignable to a variable of typeT
("x
is assignable toT
") in any of these cases:
- ...
x
is an untyped constant representable by a value of typeT
.
Since positiveNum
's underlying type is int
, which can represent 0
, the conversion happens without error.
@peterSO's answer provides a way to avoid this implicit conversion, since there is no implicit conversion from an integer constant to a struct. Note that it won't protect against a malicious user creating a value like positive.Positive{0}
, but that is usually not a concern.