Golang中如何定义列表类型?
Golang中如何定义列表类型?
我想定义一个预定义列表 ["one", "two", "three"] 的类型:
type T ????
func MyFunc(t T) {}
现在,当我调用 MyFunc 时,它应该允许使用 ["one"]、["one", "two"]、["three"] 等,但不允许使用 ["other value"]。
MyFunc(["one"]) // 正确
MyFunc(["one","two","three"]) // 正确
MyFunc(["one", "other"]) // 不正确
MyFunc([""]) // 不正确
此外,不允许重复:
MyFunc(["one", "two", "one"]) // 不正确:"one" 重复了两次
顺序无关紧要:
MyFunc(["one", "two"]) // 正确
MyFunc(["two", "one"]) // 正确
那么,我们如何定义这种特定列表的类型呢?我想我需要使用接口来实现?不确定具体如何实现。
更多关于Golang中如何定义列表类型?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
// Compiles just fine
MyFunc("Bad value literal")
这正是我不希望出现的情况。这应该由编译器来处理。因此,如果传入了错误的值,它应该抛出一个错误。
更多关于Golang中如何定义列表类型?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
好的,那么在您的 TypeScript 示例中,您将如何防范这种情况?您如何防范我的 copyOpts 示例?无论如何,您都必须:
- 进行某种运行时检查。
- 当某些东西在运行时改变了这个值时,可能会产生一些不理想的结果。
如果您在 TypeScript 中会采用后一种做法,那就直接使用 type MyString string 这个选项。如果是前者,就进行某种运行时检查。您也可以使用上面 StackOverflow 答案中链接的未导出结构体方法,如果您愿意的话。
感谢您的回答。有没有类似类型或接口的东西?而不是手动抛出错误?
我正在寻找类似于TypeScript中的行为:
// TypeScript example
interface AnimationOptions {
easing: "ease-in" | "ease-out" | "ease-in-out";
}
啊,抱歉。这个例子也没有完全满足我的需求,因为我想实现的是“其中之一”或“多个之一”的效果。但我正在寻找类似这样的东西。
skillian:
我不熟悉linter,但我怀疑可以定义一个能捕获这种情况并给出警告的linter。
我也有同样的想法,但同样对linter不够熟悉,不知道是否能为这种情况编写一个linting规则。TSC所做的或多或少相当于一个linting规则,这也是为什么它很容易被绕过(就像我上面提到的那样)。我在想是否可以使用类似这样的工具:
GitHub - quasilyte/go-ruleguard: 定义并运行基于模式的自定义linting规则
定义并运行基于模式的自定义linting规则。
并定义一个规则,禁止将字符串字面量传递给 MyFunc,这样就要求必须传入一个变量。在这种情况下,编译器会报错,提示你不能使用 string 类型作为 MyString 类型。
我的理解是:您希望将字符串限制为特定的值。也许可以按照以下思路实现?
// 用于存储有效值的全局变量
var acceptableStrings = []string{"one", "two", "three"}
func isValid(s string) bool {
for _, v := range acceptableStrings {
if v == s {
return true
}
}
return false
}
func MyFunc(items []string) error {
for _, v := range items {
if !isValid(v) {
return fmt.Errorf("Invalid value: %v", v)
}
}
return nil
}
然后进行测试:
func main() {
testCases := [][]string{
{"1", "2"},
{"First", "Second", "Third"},
{"one", "Second", "three"},
{"one"},
{"one", "two", "three"},
}
for _, v := range testCases {
if err := MyFunc(v); err != nil {
fmt.Println(err)
} else {
fmt.Println("Acceptable values:", v)
}
}
}
… 输出结果如下:
Invalid value: 1
Invalid value: First
Invalid value: Second
Acceptable values: [one]
Acceptable values: [one two three]
Program exited.
您可以在此处自行运行:
啊——一个明确表达你意图的简单方法是创建一个类型化的字符串,如下所示:
type MyString string
const (
MyStringOne MyString = "one"
MyStringTwo MyString = "two"
MyStringThree MyString = "three"
)
func MyFunc(v MyString) {
fmt.Println(v)
}
好处:这清晰地表明了意图。坏处:你可以传入一个无效的字面量值,而编译器不会在意:
// 导致以下错误:
// cannot use s (variable of type string) as MyString value in argument to MyFunc
s := "bad value"
MyFunc(s)
// 编译完全没问题
MyFunc("Bad value literal")
查看这个答案以了解不同的方法:
另外——TypeScript 提供的安全性是一种编译时检查。但你可以很容易地覆盖它,所以我认为你不能真正指望那些值永远只是你接口中定义的值。考虑以下情况:
interface AnimationOptions {
easing: "ease-in" | "ease-out" | "ease-in-out";
}
// 工作正常
let opts: AnimationOptions = {
//@ts-ignore
easing: "bad value"
};
// 工作正常
let second = copyOpts({
easing : 'uh oh'
});
function copyOpts(opts: any) : AnimationOptions {
return {...opts}
}
我的观点是:如果你想在你的 TypeScript 示例中获得真正的安全性,你仍然需要进行运行时检查。因为(至少根据我的经验)通常数据来自异步调用,而接口在那里提供了安全性的假象,但实际上几乎没有安全性。
另外,不允许重复:
MyFunc(["one", "two", "one"]) // 错误:"one" 重复了两次
我想不出在 Go 中处理这个问题的方法,但有一些变通方案可以部分解决,比如 Dean 建议的使用 const MyStringOne MyString,或者,如果你想确保无法使用其他字符串值,可以这样做:
type Something interface {
// 任何以小写字母开头的方法,这样该接口就无法被此包之外的任何东西实现。
something()
}
type one struct{}
func (one) something() { }
type two struct{}
func (two) something() { }
type three struct{}
func (three) something() { }
var (
One Something = one{}
Two Something = two{}
Three Something = three{}
)
func MyFunc(somethings ...Something) {
for _, s := range somethings {
switch s {
case One:
// 执行某些操作
case Two:
// 执行某些操作
case Three:
// 执行某些操作
default:
// 无法确保匹配到完整集合。
panic(fmt.Errorf("未知的 Something: %T", s))
}
}
}
func main() {
MyFunc(One, Two)
}
在编译时,没有什么能阻止你写:
MyFunc(One, One)
我不熟悉 linter,但我怀疑可以定义一个 linter 来捕获这种情况并给出警告。
在 TypeScript 中有办法捕获这个吗?我想知道如果你有下面的代码会发生什么:
type Something = "one" | "two" | "three"
function MyFunc(somethings: Something[]) {
somethings.push("one");
return myFuncInternal(somethings)
}
function myFuncInternal(somethings: Something[]) {
}
在Go中实现这种约束需要使用类型系统和运行时验证的组合。以下是实现方案:
package main
import (
"fmt"
"strings"
)
// 定义允许的值集合
var allowedValues = map[string]bool{
"one": true,
"two": true,
"three": true,
}
// 定义自定义类型
type T []string
// 验证函数
func validateT(t T) error {
if len(t) == 0 {
return fmt.Errorf("列表不能为空")
}
seen := make(map[string]bool)
for _, value := range t {
// 检查是否允许的值
if !allowedValues[value] {
return fmt.Errorf("值 '%s' 不在允许的列表中", value)
}
// 检查重复
if seen[value] {
return fmt.Errorf("值 '%s' 重复出现", value)
}
seen[value] = true
}
return nil
}
// 工厂函数创建验证后的T
func NewT(values ...string) (T, error) {
t := T(values)
if err := validateT(t); err != nil {
return nil, err
}
return t, nil
}
// 必须使用的方法
func MyFunc(t T) {
// 在函数入口处验证
if err := validateT(t); err != nil {
panic(err)
}
fmt.Printf("调用MyFunc: %v\n", t)
}
// 可选:为T类型添加方法
func (t T) String() string {
return fmt.Sprintf("[%s]", strings.Join(t, ", "))
}
func main() {
// 正确用例
t1, _ := NewT("one")
MyFunc(t1)
t2, _ := NewT("one", "two")
MyFunc(t2)
t3, _ := NewT("two", "one")
MyFunc(t3)
t4, _ := NewT("one", "two", "three")
MyFunc(t4)
// 错误用例
_, err1 := NewT("other")
fmt.Println("错误1:", err1)
_, err2 := NewT("one", "one")
fmt.Println("错误2:", err2)
_, err3 := NewT("")
fmt.Println("错误3:", err3)
// 直接使用字面量(不推荐)
// MyFunc(T{"one", "two"}) // 这会在运行时panic
}
更严格的实现使用私有类型和构造函数:
package main
import (
"fmt"
"strings"
)
type t struct {
values []string
}
func (t t) Values() []string {
return t.values
}
func (t t) String() string {
return fmt.Sprintf("[%s]", strings.Join(t.values, ", "))
}
func NewT(values ...string) (t, error) {
allowed := map[string]bool{"one": true, "two": true, "three": true}
seen := make(map[string]bool)
if len(values) == 0 {
return t{}, fmt.Errorf("列表不能为空")
}
for _, v := range values {
if !allowed[v] {
return t{}, fmt.Errorf("值 '%s' 不在允许的列表中", v)
}
if seen[v] {
return t{}, fmt.Errorf("值 '%s' 重复出现", v)
}
seen[v] = true
}
return t{values: values}, nil
}
func MyFunc(t t) {
fmt.Printf("调用MyFunc: %v\n", t.Values())
}
func main() {
t1, _ := NewT("one")
MyFunc(t1)
t2, _ := NewT("one", "two")
MyFunc(t2)
// 编译时无法阻止的错误
_, err := NewT("other")
if err != nil {
fmt.Println("错误:", err)
}
}
如果需要编译时检查,可以使用代码生成工具,但会增加复杂性。上述方案在运行时提供验证,这是Go中处理此类约束的典型方式。


