【问题标题】:Why is a custom struct accepted in place of an error?为什么接受自定义结构来代替错误?
【发布时间】:2020-04-08 02:27:51
【问题描述】:

学习 Go 并参考https://tour.golang.org/methods/20

package main

import (
    "fmt"
    "math"
)

type ErrNegativeSqrt float64 //This is the custom Struct

func (e ErrNegativeSqrt) Error() string { 
    return fmt.Sprintf("cannot Sqrt negative number: %g", float64(e))
}

func Sqrt(x float64) (float64, error) {  // Is error a type ?
    if(x < 0){
        return x, ErrNegativeSqrt(x) //Q1) If error is a type, How come  ErrNegativeSqrt(x) is of type error?
    }       
    z := float64(1.5)
    val := float64(0)
    for {
        z = z - (z*z-x)/(2*z)
        if math.Abs(val-z) < 1e-10 {
            break
        }
        val = z
    }
    return val, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Q2) 为什么在 Error 方法中调用 fmt.Sprint(e) 会使程序陷入死循环?

【问题讨论】:

  • Q2) 发生无限递归是因为 fmt 在后台查看提供的值,如果该值实现特定接口,它将调用其主要方法,在本例中为 Error 方法error 接口,但实现Stringer 接口的类型也容易受到此影响。因此,在这些类型的方法中,如果他们想将接收器传递给 fmt,则将接收器转换为不实现这些接口的类型,就像您在 float64(e) 的示例中看到的那样,这一点很重要。跨度>
  • 您的标题询问了自定义结构,但您的示例甚至没有使用严格的结构。这是一个 float64。

标签: go struct types error-handling


【解决方案1】:

Go 的 interface 很棘手。需要对它进行一些试验才能很好地了解如何使用它。这就是为什么您正在使用的教程让您使用它编写东西:您必须实际使用它一段时间才能了解如何使用它。 :-)

这里值得纠正一些缺点:

type ErrNegativeSqrt float64 //This is the custom Struct

这不是struct 类型。 float64 本身是一个预先声明的数字类型as defined here in the Go reference:

数字类型表示整数或浮点值的集合。预先声明的独立于体系结构的数字类型是:[这里剪掉了各种条目]

float64     the set of all IEEE-754 64-bit floating-point numbers

您的type 声明在这里创建了一个名为ErrNegativeSqrt 的新类型,它以float64 作为其基础类型;见the Types section

一种类型——任何类型,无论是否struct——要么实现接口类型,要么不。接口类型很复杂。请参阅the Interface types section 了解全貌。 Go 的error 是一个预先声明的接口类型,由the Errors section 定义:

预声明的类型error定义为

type error interface {
    Error() string
}

这意味着每当你定义自己的类型UserT,如果你添加这样一行:

func (varname UserT) Error() string {
    // return some string here
}

您定义的类型 UserT 现在实现了错误接口。因此,您可以通过简单的赋值将任何 UserT 类型的值转换error。请参阅the Assignability section,其中包括:

x可分配T类型的变量...如果以下条件之一适用:[一些项目符号被剪断]

  • T 是一个接口类型,x 实现了T

我们刚刚看到error 是一个接口类型。因此,如果某些情况需要error 类型的变量,并且您有一些UserT 类型的现有值x 其中UserT 有一个方法Error() string,您可以将该值x 分配到error 类型的变量。也就是说,假设 ErrNegativeSqrt 像您一样设置,并且:

var x ErrNegativeSqrt
// set `x` to some valid value

// and:
var e error

然后:

e = x

是有效的并将x分配给e,尽管e本身的类型是一个接口类型,而不是ErrNegativeSqrt p>

当您像这样将x 的值的副本放入 e 时,编译器实际上在e 中设置了两个 东西。换句话说,e 的行为很像一个二元素数组,或者一个带有两个字段的 struct,只是您不能使用下标或 .field 类型的拼写访问这两个字段。

这两个值之一(e 值的前半部分)是x具体类型。这两个值中的另一个是x 的实际值,复制到e 的另一半。

换句话说,编译器转:

e = x

大致相当于:

e.value = x
e.type = ErrNegativeSqrt

除了你实际上不能e.typee.value。编译器自动为您完成两部分化

You should have already seen how you can pick apart the two parts of things stored into e if necessary. 通常不是必要的,尤其是在界面设计良好的情况下。但在尝试了#20 中的错误返回之后,值得回溯到巡回赛的这些早期部分。

【讨论】:

  • 这是继续理解接口值tapirgames.com/blog/golang-interface-implementation的内部表示
  • @torek 非常感谢您撰写详细的答案,这对像我这样的新手很有帮助。几个问题 1)这些是 Golang 中仅有的 8 种基本(内置语言)类型吗? 2) 字面意思是什么?我仍然无法理解文字。当他们说字符串字面量时,映射字面量是什么?
  • @torek 幕后编译器的事情 e = x 解释是无法言喻的。这些都是怎么学的?
  • 我不确定您是如何计算 8 种内置类型的。规范 (golang.org/ref/spec) 调用了 bool + 17 个数字类型 + 加上 string。数组、切片、结构、指针、函数和接口类型都建立在这些类型之上,所以我可能会数出这些原始类型中的 19 种。确实,某些数字类型具有相同的基础类型(int 匹配,例如,int32int64),但它们仍然不同。但是,byterune别名 类型,因此我们可以从 19 中减去 2 得到 17。但确切的数字并不是那么重要。
  • 在这一点上——至少已经阅读了两次规范,或者甚至在你第二次通过它的时候——你已经准备好查看上面@mh-cbon 的链接之类的东西,它谈到了如何当使用接口和诸如reflect 之类的东西时,编译器和运行时会共同查找 方法。这类事情不是规范的一部分,故意的,以便编译器和运行时的人们有一天可以继续进行更新和更有效的阴谋,如果他们能想到的话。跨度>
【解决方案2】:

Error 是一个定义Error() string 方法的接口。如果您的错误实现了该接口,那很好。

func (e ErrNegativeSqrt) Error() string { 
    return fmt.Sprintf("cannot Sqrt negative number: %g", float64(e))
}

而该代码正是这样做的。现在你的ErrNegativeSqrt 类型实现了错误接口。

if(x < 0){
        return x, ErrNegativeSqrt(x) // totally fine.
    }

error 的文档

【讨论】:

  • 所以基本上当我阅读 Go 代码时,我如何才能确定这里有一个正在实现的接口?
  • @sofs1 Go 没有隐式类型转换,所以如果你发现需要的类型与提供的类型不一样,那么它要么是实现的接口,要么在极少数情况下是类型别名.
  • @sofs1 编译时检查的接口实现。但是您可以选择明确地检查here
猜你喜欢
  • 1970-01-01
  • 2017-11-01
  • 2022-01-20
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多