Is not that confusing?

Let’s look at the code. Is not it confusing that in first case driver is a package and in the second case db is an object?
Of course, you could say while you code you know what is an object and what is a package. Yes, but it’s probably true while I’m coding. Then after some time other dude opens the project and starts guessing what is what there.
For example, in Java, there is no such unclearness because of a notation var.do() always means that var is an object.

var db *sql.DB

func main() {
	db = driver.ConnectDB()
	defer db.Close()
}

db is a pointer to a sql.DB type. I’m not sure though where driver is coming from, you have neither imported it, nor did you declare it.

Imported but not showed there of course.

Then its not an “object” but a package.

It’s a package

package driver

import ( ...

Another example

router := mux.NewRouter()
controller := controllers.Controller{}

The conceptual difference is in () and {}. It looks quite similar but a complete difference in a sense. Quite confusing too.

If I understand correctly, your primary complaint seems to be that you can shadow an imported package with a variable. Eg in the example below, fmt is a package but then gets shadowed when we create a variable named fmt and from that point onwards inside of main() it doesn’t reference the package.

package main

import (
	"fmt"
)

func main() {
	fmt := fmt.Println
	fmt("hi")
}

Playground link: https://play.golang.org/p/QduMG8JnLV8

I understand how this might feel confusing, which is why most developers tend to avoid doing this. As you said, it can lead to confusion.

When this DOES happen, it tends to be very intentional and done because package level functions might be more useful if we had more context of some sort. For instance, https://github.com/matryer/is does this because you really don’t tend to use the package functions and instead want something that wraps the individual testing.T from each test case.

This isn’t limited to just packages either. For instance, you can shadow regular old variables in Go:

package main

import (
	"fmt"
)

func main() {
	x := 14
	fmt.Println(x)
	for i := 0; i < 2; i++ {
		x := i * 2
		fmt.Println("in for loop:", x)
	}
	fmt.Println("out of for loop:", x) // unchanged!
	
	// You can even do this with just curly braces and assign to the original x
	{
		x := x
		x *= 3
		fmt.Println("in curly:", x)
	}
	fmt.Println("out of curly:", x) // unchanged!
}

Playground link: https://play.golang.org/p/DDTggNumuiN

At one point you make the argument that Java doesn’t have this “unclearness”, but it actually does in many ways. For instance, all of this shadowing of variables I mentioned is indeed possible in Java and is illustrated here: https://www.dummies.com/programming/java/shadowed-classes-or-variables-in-java/ (this article may suck, as I’m not up-to-date with what Java articles are good).

Again, it is discouraged from being used too much because, as you pointed out, it can cause confusion for future developers, but Java DOES permit it.

The only thing really unique to Go is that you can shadow a package name with a new variable, but I’m not really sure this is such an awful thing. As we saw with the is package, it can be very helpful in designing some packages where you need additional context and then once you shadow the package name you never use it again anyway. But, as with all things, it could indeed be abused and lead to super confusing code so you should be cautious with how you use it.

As to your last comment about the difference between {} and () - these both do very different things, and it sounds like you just need to learn how each one works in Go. Yes, it is different from some other languages. Yes, that might lead to confusion at first. But what is the point of creating another language identical to Java or some other language? Go tried a different approach and both myself and many others have come to prefer it over time.

2 Likes

Jon, my primary complaint is that Go uses similar grammar constructions for completely different ideas. For instance, in Java, a situation where we use a “point” i.e. “.”:

Element element = (Element) nodes.item(i);
NodeList title = element.getElementsByTagName("title");
Element line = (Element) title.item(0);

What is common here, how do you think? Yes, the idea of that after a “point” there is method. But what is before that point? Some object. And it’s clear.
In Golang it’s quite not so intuitive:

db = driver.ConnectDB()
defer db.Close()

There are methods here too (ok, functions, to be fair), but in one case we use package name before function and in the next line we use an object name. So we are using similar grammar syntax with different senses. Caught the idea?
And my second example mentioned earlier:

router := mux.NewRouter()
controller := controllers.Controller{}

What is common here? Yes, the idea of using some actions (i.e. “void” actions). So we read it like: ok, there is mux package and we use some function from it… ok let it be… we’ve got some router, ok… Next, we’re reading next line which starts quite similar like: we are getting some controller by calling some “action” on controllers object (or package?! yes, I know it’s package… ) but waite… it’s a completely different idea here because they are curly… ok… Caught the idea of confusing while reading this code?

Java also uses dots for completely different ideas.

String cn = Customer.name // a class field
String s = customer.name // an object field
String n = customer.getName() // a method
import customer.name // a package
new customer.Info // a class

In practice it isn’t a problem, because contextual information lets you work out which is meant.

1 Like

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