抽象数据类型构造函数可以被不小心绕过吗?

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 type T ("x is assignable to T") in any of these cases:

  • ...
  • x is an untyped constant representable by a value of type T.

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.

Of course it compiles. There is nothing that prevents a value of type positiveNum to be zero or less. The only runtime check you have is in MakePositiveNum, which you never call when you do the following:

m.UsePositiveNum(0)

Every function/method that receives a value of type positiveNum must do the validation, not only MakePositiveNum if you want to be sure. Else you have to assume the developer will always use MakePositiveNum to create the value.

You can find something similar in the standard library with image.Rectangle. Many of it´s methods assume that Min.X <= Max.X and Min.Y <= Max.Y, but there is no actual validation, only the guarantee that:

A rectangle's methods always return well-formed outputs for well-formed inputs.

You may be looking for something like this:

ADT:

package positive

type Positive struct{ i uint64 }

func New(i int) Positive {
    if i < 1 {
        panic("not a positive number")
    }
    return Positive{i: uint64(i)}
}

func Function(p Positive) Positive { return p }

func (p Positive) Method() Positive { return p }

func (p Positive) Integer() uint64 { return p.i }

Usage:

package main

import "positive"

func main() {
    pn := positive.New(123)
    i := 1; positive.Function(i) // fails
    UseInt(pn) // fails
    pn = positive.Positive(0) // fails
    positive.Function(pn)
    positive.Function(0) // fails
}

func UseInt(int) {}