抽象数据类型的构造函数可以被意外绕过吗?

huangapple go评论87阅读模式
英文:

Abstract data type constructor can be accidentally bypassed?

问题

我正在尝试创建一个表示正数的抽象数据类型:

package m

type positiveNum int

func MakePositiveNum(i int) positiveNum {
    if i < 1 { panic("non positive number") }
    return positiveNum(i)
}

// 一些期望正数的函数
func UsePositiveNum(s positiveNum) {}

以下是一些示例用法:

package main
import "m"
func main() {
    pn := m.MakePositiveNum(123)
    //i := 1; m.UsePositiveNum(i) // 预期失败,因为传递的是 int 而不是 positiveNum
    //useInt(pn) // 预期失败,因为尝试传递 positiveNum 而不是 int
    //pn = m.positiveNum(0) // 预期失败,因为该类型是私有的
    m.UsePositiveNum(pn)
}

func UseInt(int) {}

如果你将 m.UsePositiveNum(pn) 替换为 m.UsePositiveNum(0),它仍然可以编译通过,绕过了正数类型检查。为什么会这样呢?

英文:

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 &lt; 1 { panic(&quot;non positive number&quot;) }
    return positiveNum(i)
}

// some function that expects a positive number
func UsePositiveNum(s positiveNum) {}

Here are some example uses:

package main
import &quot;m&quot;
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?

答案1

得分: 2

这里发生的情况是,0 是一个未指定类型的常量。这种常量受到关于可赋值性的规则的约束:

一个值 x 在以下任何情况下都可以赋值给类型为 T 的变量("x 可以赋值给 T"):

  • ...
  • x 是一个可以用 T 类型的值表示的未指定类型的常量。

由于 positiveNum 的底层类型是 int,可以表示为 0,所以转换可以顺利进行。

@peterSO 的答案提供了一种避免这种隐式转换的方法,因为从整数常量到结构体没有隐式转换。请注意,这并不能防止恶意用户创建像 positive.Positive{0} 这样的值,但通常这不是一个问题。

英文:

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.

答案2

得分: 1

当然可以编译。没有任何东西阻止类型为positiveNum的值为零或更小。你唯一的运行时检查在MakePositiveNum中,但你在执行以下操作时从未调用它:

m.UsePositiveNum(0)

每个接收positiveNum类型值的函数/方法都必须进行验证,不仅仅是MakePositiveNum,如果你想确保。否则,你必须假设开发者将始终使用MakePositiveNum来创建该值。

你可以在标准库中找到类似的例子,比如image.Rectangle。它的许多方法假设Min.X <= Max.XMin.Y <= Max.Y,但实际上没有进行验证,只有以下保证:

对于合法输入,矩形的方法始终返回合法的输出。

英文:

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.

答案3

得分: 1

你可能正在寻找类似这样的内容:

ADT(抽象数据类型):

package positive

type Positive struct{ i uint64 }

func New(i int) Positive {
    if i < 1 {
        panic("不是正数")
    }
    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 }

用法:

package main

import "positive"

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

func UseInt(int) {}
英文:

You may be looking for something like this:

ADT:

package positive

type Positive struct{ i uint64 }

func New(i int) Positive {
	if i &lt; 1 {
		panic(&quot;not a positive number&quot;)
	}
	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 &quot;positive&quot;

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) {}

huangapple
  • 本文由 发表于 2014年9月1日 12:31:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/25598863.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定