英文:
How to unite two different struct using interface?
问题
我有以下代码:
package main
import (
"log"
)
type Data struct {
Id int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
func main() {
response := Data{Id: 100, Name: `Name`}
if true {
response = DataError{Message: `message`, ErrorCode: `code`}
}
log.Println(response)
}
这段代码给我返回了一个错误:
./start.go:20: cannot use DataError literal (type DataError) as type
Data in assignment
看起来我不能将不同类型(在我的情况下是DataError
)的数据分配给response
变量。我听说可能的解决方案是通过接口将Data
和DataError
结构体合并。或者也许还有其他更好的解决方案?
你能告诉我如何解决这个问题吗?
谢谢
英文:
I have the following code:
package main
import (
"log"
)
type Data struct {
Id int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
func main() {
response := Data{Id: 100, Name: `Name`}
if true {
response = DataError{Message: `message`, ErrorCode: `code`}
}
log.Println(response)
}
This code returns me an error:
> ./start.go:20: cannot use DataError literal (type DataError) as type
> Data in assignment
Seems to be that I could not assign to response
var data with different type (in my case DataError
). I heard that possible solution could be to unite Data
and DataError
structs via interface. Or maybe there is another better solution?
Could you please point me how to resolve this problem?
Thanks
答案1
得分: 7
看起来你正在尝试创建一个联合类型(ML语言系列中称为"enum"的类型)。我知道几种解决这个问题的模式:
0. 基本错误处理(playground)
我猜你只是在进行基本的错误处理。在Go中,我们使用多个返回值并检查结果。这几乎肯定是你想要做的:
package main
import (
"fmt"
"log"
)
type Data struct {
ID int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
// 实现`error`接口。`error`是一个具有单个`Error() string`方法的接口
func (err DataError) Error() string {
return fmt.Sprintf("%s: %s", err.ErrorCode, err.Message)
}
func SomeFunction(returnData bool) (Data, error) {
if returnData {
return Data{ID: 42, Name: "Answer"}, nil
}
return Data{}, DataError{
Message: "A thing happened",
ErrorCode: "Oops!",
}
}
func main() {
// 这个布尔参数控制是否返回错误
data, err := SomeFunction(false)
if err != nil {
log.Fatalln(err)
}
fmt.Println(data)
}
1. 接口(playground)
同样,如果你的选项是好数据和错误,你应该使用第一种情况(坚持惯例),但有时你可能有多个“好数据”选项。我们可以使用接口来解决这个问题。在这里,我们添加了一个虚拟方法来告诉编译器将可以实现这个接口的可能类型限制为具有IsResult()
方法的类型。这种模式的最大缺点是将东西放入接口可能会产生分配,这在紧密循环中可能会有害。这种模式并不是非常常见。
package main
import "fmt"
type Result interface {
// 一个虚拟方法来限制可以满足这个接口的可能类型
IsResult()
}
type Data struct {
ID int
Name string
}
func (d Data) IsResult() {}
type DataError struct {
Message string
ErrorCode string
}
func (err DataError) IsResult() {}
func SomeFunction(isGoodData bool) Result {
if isGoodData {
return Data{ID: 42, Name: "answer"}
}
return DataError{Message: "A thing happened", ErrorCode: "Oops!"}
}
func main() {
fmt.Println(SomeFunction(true))
}
2. 带标签的联合(playground)
这种情况与前一种情况类似,只是我们使用一个带有标签的结构体来告诉我们结构体包含的数据类型是什么(这类似于C中的带标签的联合,只是结构体的大小是其潜在类型的和,而不是最大潜在类型的大小)。虽然这占用了更多的空间,但它可以轻松地在堆栈上分配,从而使其在紧密循环中更加友好(我使用这种技术将分配从O(n)减少到O(1))。在这种情况下,我们的标签是一个布尔值,因为我们只有两种可能的类型(Data和DataError),但你也可以使用类似C的枚举。
package main
import (
"fmt"
)
type Data struct {
ID int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
type Result struct {
IsGoodData bool
Data Data
Error DataError
}
// 实现`fmt.Stringer`接口;这会被fmt.Println()和相关函数自动检测和调用
func (r Result) String() string {
if r.IsGoodData {
return fmt.Sprint(r.Data)
}
return fmt.Sprint(r.Error)
}
func SomeFunction(isGoodData bool) Result {
if isGoodData {
return Result{
IsGoodData: true,
Data: Data{ID: 42, Name: "Answer"},
}
}
return Result{
IsGoodData: false,
Error: DataError{
Message: "A thing happened",
ErrorCode: "Oops!",
},
}
}
func main() {
// 这个布尔参数控制是否返回错误
fmt.Println(SomeFunction(true))
}
希望这些代码能帮到你!
英文:
It looks like you're trying to make a union type (what the ML-family of languages calls "enum"). I know of a couple of patterns for this:
0. Basic error handling (playground)
I suspect what you're doing is just basic error handling. In Go, we use multiple return values and check the result. This is almost certainly what you want to do:
package main
import (
"fmt"
"log"
)
type Data struct {
ID int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
// Implement the `error` interface. `error` is an interface with
// a single `Error() string` method
func (err DataError) Error() string {
return fmt.Sprintf("%s: %s", err.ErrorCode, err.Message)
}
func SomeFunction(returnData bool) (Data, error) {
if returnData {
return Data{ID: 42, Name: "Answer"}, nil
}
return Data{}, DataError{
Message: "A thing happened",
ErrorCode: "Oops!",
}
}
func main() {
// this bool argument controls whether or not an error is returned
data, err := SomeFunction(false)
if err != nil {
log.Fatalln(err)
}
fmt.Println(data)
}
1. Interfaces (playground)
Again, if your options are good-data and error, you should probably use the first case (stick with the idiom/convention), but other times you might have multiple "good data" options. We can use interfaces to solve this problem. Here we're adding a dummy method to tell the compiler to constrain the possible types that can implement this interface to those that have an IsResult() method. The biggest downside to this is that sticking things into an interface can incur an allocation, which can be detrimental in a tight loop. This pattern isn't terribly common.
package main
import "fmt"
type Result interface {
// a dummy method to limit the possible types that can
// satisfy this interface
IsResult()
}
type Data struct {
ID int
Name string
}
func (d Data) IsResult() {}
type DataError struct {
Message string
ErrorCode string
}
func (err DataError) IsResult() {}
func SomeFunction(isGoodData bool) Result {
if isGoodData {
return Data{ID: 42, Name: "answer"}
}
return DataError{Message: "A thing happened", ErrorCode: "Oops!"}
}
func main() {
fmt.Println(SomeFunction(true))
}
2. Tagged Union (playground)
This case is similar to the previous case, except instead of using an interface, we're using a struct with a tag that tells us which type of data the struct contains (this is similar to a tagged union in C, except the size of the struct is the sum of its potential types instead of the size of its largest potential type). While this takes up more space, it can easily be stack-allocated, thus making it tight-loop friendly (I've used this technique to reduce allocs from O(n) to O(1)). In this case, our tag is a bool because we only have two possible types (Data and DataError), but you could also use a C-like enum.
package main
import (
"fmt"
)
type Data struct {
ID int
Name string
}
type DataError struct {
Message string
ErrorCode string
}
type Result struct {
IsGoodData bool
Data Data
Error DataError
}
// Implements the `fmt.Stringer` interface; this is automatically
// detected and invoked by fmt.Println() and friends
func (r Result) String() string {
if r.IsGoodData {
return fmt.Sprint(r.Data)
}
return fmt.Sprint(r.Error)
}
func SomeFunction(isGoodData bool) Result {
if isGoodData {
return Result{
IsGoodData: true,
Data: Data{ID: 42, Name: "Answer"},
}
}
return Result{
IsGoodData: false,
Error: DataError{
Message: "A thing happened",
ErrorCode: "Oops!",
},
}
}
func main() {
// this bool argument controls whether or not an error is returned
fmt.Println(SomeFunction(true))
}
答案2
得分: 1
你不能将两个不可“赋值”的不同类型分配给同一个变量...除非你使用特定的接口签名或空接口。
https://golang.org/ref/spec#Assignability
以下代码可以编译通过:
func main() {
var response interface{} // 空接口,也称为Object或void指针
response = Data{Id: 100, Name: `Name`}
if true {
response = DataError{Message: `message`, ErrorCode: `code`}
}
log.Println(response)
}
因为每个类型都实现了空接口,但你只想在没有其他选项时才这样做。
如果两个类型共享一些方法,可以使用特定的接口,例如(伪代码):
type Responder interface {
Respond() string
}
type Data struct { /* 代码 */ }
func (d Data) Respond() string { return "" }
type DataError struct { /* 代码 */ }
func (d DataError) Respond() string { return "" }
func main() {
var response Responder // 声明为接口
response = Data{}
response = DataError{}
fmt.Println(response)
}
当你有疑问时,快速查阅Go规范是有用的,它是唯一的权威,并且相对于大多数规范来说写得非常清晰。在大多数情况下,规则是明确的,这是Go的优势。
英文:
You can't assign 2 different types that are not "assignable" to the same variable ... unless you use a specific interface signature or empty interface.
https://golang.org/ref/spec#Assignability
that code would compile :
func main() {
var response interface{} // empty interface AKA Object AKA void pointer
response = Data{Id: 100, Name: `Name`}
if true {
response = DataError{Message: `message`, ErrorCode: `code`}
}
log.Println(response)
}
since every type implements empty interface, but you want to do that only if there is no other options.
if 2 types share some methods use a specific interface, for instance (pseudo-code) :
type Responder interface {
Respond() string
}
type Data struct { /* code */
}
func (d Data) Respond() string { return "" }
type DataError struct { /* code */
}
func (d DataError) Respond() string { return "" }
func main() {
var response Responder // declared as interface
response = Data{}
response = DataError{}
fmt.Println(response)
}
Whenever you have doubts a quick scan of the go spec is useful, it is the only authority and pretty well written compared to most specs out there. For the most part the rules are crystal clear, and that's a strength of Go.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论