Casting and ambiguous return type from functions

Hello I am new in Golang and need some help, am a Java programmer

Look at this line of code

1) f := goutil.Try(os.Open("file")).(io.Reader)

My util Try manages the error cases etc etc, then return an interface and cast for f.

If I use simply, as taught on the documentation, the following code

2) f := os.Open("file")

f in this case is a File !
So Why above I have to convert in io.Reader ? That is ok and work fine !

In other word why these two piece of code, work anyway !

f := goutil.Try(os.Open("file")).(io.Reader)

AND

f := os.Open("file")
defer f.Close()

my Try function is for now simply

func Try(res interface{}, err error) interface{}{
	
	if err != nil {
		panic(err)
		os.Exit(-1)
	}
	
	return res
}

Thank you in adavnced

Hi @7ab10_7ab10 , welcome to the forum.

First, a cautionary advice. Don’t try to shoehorn Java idioms into Go. Go uses explicit error handling for a reason. If you roll your own Java-style try-catch solution, you make your code harder to read for other Gophers.

Regarding your question, the io.Reader interface and all related io interfaces are a very convenient way to abstract away the fact that f is a file.

Consider you have a function that reads from a file, and you want to use that function to read from a string or a network connection instead, bad luck.

However, if that function reads from an io.Reader, you can use this function for reading files, strings, network connections, buffers, or anything that implements the io.Reader interface.

1 Like

Hello @christophberger
thank you for your quick answer.

Well,
I know about the error handling, but I use it only for simple system programming scripts with bash where I am not handling errors, if it passes ok otherwise exit and goodbye…

Anyway
Yes, I also understand the fact of the Reader, as for the Writer, as in Java and in fact sometimes in Java it is boring…

But what doesn’t add up, apart from this fact, is how the golang compiler can manage two types that are different semantically, because File is not an empty struct and has its own (hidden) fields, so even for the io.Reader.

I mean
Why in the first case it manages to cast in io.Reader while in the second case it returns a File.
In golang there are no Inerithance or interface implementation as in Java so they are two different structs, at the least for all that I know

Thank you

Because the compiler knows that File has the methods that are required for it to implement Reader. FWIW, you don’t need to convert the File to a Reader. You can just pass it to functions that accept a Reader.

I’m not sure what you mean here:

You’re correct that *os.File is not an empty struct, but I don’t know what you mean about io.Reader, which is an interface, not a struct, and so it has no fields.

An interface is a description of a method set. The method set of io.Reader is a single method: Read([]byte) (int, error). Any struct (or integer, string, slice, etc.) type will implement the io.Reader interface if they have the same Read([]byte) (int, error) method.

This code:

f := goutil.Try(os.Open("file")).(io.Reader)

Essentially does this:

var temp interface{}
var err error
temp, err = os.Open("file")
if err != nil {
    panic(err)
    os.Exit(-1)
}
var f io.Reader = temp.(io.Reader)

temp is an interface{} variable which contains an *os.File. When you assert that the interface{} is an io.Reader, the Go runtime checks if whatever is in the interface{} has a Read([]byte) (int, error) method. If it does, the assertion succeeds and you get an io.Reader out. Otherwise, the assertion will panic. If you get rid of the goutil.Try, and just do:

f, err := os.Open("file")

Then f is an *os.File because it was never converted to an interface{} or an io.Reader.

1 Like

Thank you @skillian

I got the mechanism.
Well, I am a Java programmer and what you explained is golang way to implement an interface, it is very interesting information for me.

So, to check if I perfectly understood, this should be correct:
I can convert or cast any interface to any type provided that both contain the same method name and signatures.
So for example

type Lion struct {
… chase
}

could be casted (if possible) in

type Car struct {
chase … anyway
}

just because they would have the same method name and signature

Or it is need at least an interface with that method and signature ^

Correct ?

Thank you

No. I don’t know Java, but I’ll try my best to write some Java pseudo-code to explain it.

Imagine we have these types in Java:

public interface Chaseable {
    public void chase();
}

public class Lion implements Chaseable {
    public void chase() { }
}

public class Car implements Chaseable {
    public void chase() { }
}

I would write it this way in Go:

type Chaser interface {
    chase()
}

type Lion struct {}

func (x Lion) chase() { }

type Car struct{}

func (x Car) chase() { }

I suspect you cannot cast a Lion to a Car in Java. You also cannot do that in Go, however I suspect you can store either a Lion or a Car in a Chaseable in Java, and you can do the same thing in Go:

Chaseable c = new Lion();
c = new Car();
var c Chaser = Lion{}
c = Car{}

I’m going out on a limb here, but if I were to rewrite your goutil.Try for Java, I think its analogy would be:

Object temp;
//Exception err; // Java of course uses exceptions
temp = new File("file");
// ignore panic and os.Exit; I think you'd get an exception.
Serializable f = (Serializable)temp;

In Go, you can:

  • Convert concrete types (anything that’s not an interface), or
  • Type-assert interfaces into interfaces.

In Java, I think you have the same thing but I think they use the same syntax.

2 Likes

Ok Thank you
I thought io.Reader was a struct with a defined body. I should wear some glasses :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.