-2

Learning Go and with reference to 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) Why does a call to fmt.Sprint(e) inside the Error method will send the program into an infinite loop?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
sofs1
  • 3,834
  • 11
  • 51
  • 89
  • 3
    Q2) the infinite recursion happens because `fmt` under the hood looks at the provided value and if that value implements a specific interface it will invoke its primary method, in this case the `Error` method of the `error` interface, but also types implementing the `Stringer` interface are susceptible to this. Therefore it is important in these kinds of methods, if they want to pass the receiver to `fmt`, to convert the receiver to a type that doesn't implement those interfaces, like you see in the example with `float64(e)`. – mkopriva Dec 15 '19 at 10:29
  • Your title asked about a custom struct, but your example doesn't even use a strict. It's a float64. – Jonathan Hall Dec 15 '19 at 15:27

2 Answers2

2

Go's interface is tricky. It takes some experimenting with it to get a good feel for how to use it. That's why the tutorial you're working with has you write things using it: you have to actually use it for a while before you can get to the point where you understand how to use it. :-)

It's worth correcting some nits here though:

type ErrNegativeSqrt float64 //This is the custom Struct

This is not a struct type. float64 itself is a predeclared numeric type, as defined here in the Go reference:

A numeric type represents sets of integer or floating-point values. The predeclared architecture-independent numeric types are: [various entries snipped out here]

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

Your type declaration here creates a new type named ErrNegativeSqrt that has float64 as its underlying type; see the Types section.

A type—any type, whether struct or not—either implements an interface type, or does not. An interface type is, well, complicated; see the Interface types section for the full picture. Go's error is a predeclared interface type, as defined by the Errors section:

The predeclared type error is defined as

type error interface {
    Error() string
}

This means that whenever you define your own type UserT, if you add a line like this:

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

your defined type UserT now implements the error interface. As such, you can convert any value of type UserT to error via simple assignment. See the Assignability section, which includes:

A value x is assignable to a variable of type T... if one of the following conditions applies: [some bullet items snipped]

  • T is an interface type and x implements T

We just saw that error is an interface type. So, if some situation calls for a variable of type error, and you have some existing value x of type UserT where UserT has a method Error() string, you can assign that value x into the variable of type error. That is, given ErrNegativeSqrt set up as you did, and:

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

// and:
var e error

then:

e = x

is valid and assigns x into e, though the type of e itself is an interface type, rather than ErrNegativeSqrt.

When you stuff a copy of the value of x into e like this, the compiler actually sets up two things inside e. In other words, e acts a lot like a two-element array, or a struct with two fields, except that you can't access the two fields with subscripts or .field type spellings.

One of these two values—the first half of e's value—is x's concrete type. The other of these two values is x's actual value, copied into the other half of e.

In other words, the compiler turns:

e = x

into the rough equivalent of:

e.value = x
e.type = ErrNegativeSqrt

except, again, for the fact that you can't actually write e.type and e.value. The compiler does the two-part-izing for you, automatically.

You should have already seen how you can pick apart the two parts of things stored into e if necessary. It's often not necessary, especially if the interface is well designed. But it's worth backtracking to these earlier sections of the Tour after experimenting with error returns in #20.

torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    this is a continuation to understand internal representation of an interface value https://www.tapirgames.com/blog/golang-interface-implementation –  Dec 15 '19 at 17:50
  • @torek Thank you very much for writing a detailed answer which really helps newbies like me. Few questions 1) Are these the only 8 basic (in-built in language) types in Golang? 2) What is meant by literal? I still can't wrap my head around literals. When they say String literal, map literal what are they? – sofs1 Dec 15 '19 at 21:23
  • 1
    @torek That behind the scenes compiler thing e = x explanation is beyond words. How do you guys learn all these? – sofs1 Dec 15 '19 at 21:40
  • I'm not sure how you are counting 8 in-built types. The spec (https://golang.org/ref/spec) calls out `bool` + 17 numeric types + plus `string`. Array, slice, struct, pointer, function, and interface types are built atop these, so I'd perhaps count 19 of these primitive types. It's true that some of the numeric types have the same underlying type (`int` matches, underneath, `int32` or `int64` for instance) but these are still different. However, `byte` and `rune` are type *aliases* so we can subtract 2 from the 19 to get 17. But the exact number isn't all that important. – torek Dec 15 '19 at 22:20
  • As for literals, Go's are pretty conventional: they're defined starting at https://golang.org/ref/spec#Integer_literals going down through https://golang.org/ref/spec#String_literals. What are unusual in Go are *constants* (https://golang.org/ref/spec#Constants) which may be untyped. – torek Dec 15 '19 at 22:23
  • To learn these, it helps to go through the Go Tour *more than once*. I've found that doing these information-dense tutorials at least twice, and sometimes three times, is wise: you miss a lot of things on the first pass, especially if you are used to learning from Huge Books Of Everything. Then, having done that, read through the entire Go spec (probably also two or three times, over the course of a month or more). Not everything will stick, but—at least for me—I'll remember: *oh, there was something in the spec about ____*. – torek Dec 15 '19 at 22:30
  • 1
    At this point—having read the spec at least twice, or maybe even during your second pass through it—you're ready to look at things like @mh-cbon's link above, which talks about how the compiler and runtime conspire to *look up* methods when using interfaces and stuff like `reflect`. This sort of thing *isn't* part of the spec, on purpose, so that the compiler and runtime people can move on to a newer and more efficient conspiracy someday, if they can think of one. – torek Dec 15 '19 at 22:32
1

Error is an interface that defines an Error() string method. If your error implements that interface it is fine.

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

And that code exactly does it. Now your ErrNegativeSqrt type implements error interface.

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

Documentation for error

Eldar
  • 9,781
  • 2
  • 10
  • 35
  • So basically when I read Go code, how can I identify that there is an interface which is being implemented here? – sofs1 Dec 15 '19 at 10:13
  • 2
    @sofs1 Go has no implict type conversion, so if you find the required type is not the same of the provided type, then it is either an interface implemented, or in a rare case, a type alias. – leaf bebop Dec 15 '19 at 10:24
  • @sofs1 Interface implementation checked in the compile time. But you have the option check that explicitly as mentioned [here](https://stackoverflow.com/questions/27803654/explanation-of-checking-if-value-implements-interface) – Eldar Dec 15 '19 at 10:32