-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?

2
  • 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). Commented Dec 15, 2019 at 10:29
  • Your title asked about a custom struct, but your example doesn't even use a strict. It's a float64. Commented Dec 15, 2019 at 15:27

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

7 Comments

this is a continuation to understand internal representation of an interface value tapirgames.com/blog/golang-interface-implementation
@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?
@torek That behind the scenes compiler thing e = x explanation is beyond words. How do you guys learn all these?
I'm not sure how you are counting 8 in-built types. The spec (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.
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.
|
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

3 Comments

So basically when I read Go code, how can I identify that there is an interface which is being implemented here?
@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.
@sofs1 Interface implementation checked in the compile time. But you have the option check that explicitly as mentioned here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.