How can I not reassign a variable in go? [duplicate] - go

This question already has answers here:
What is "_," (underscore comma) in a Go declaration?
(9 answers)
Closed 2 years ago.
I don't understand how to reassign a variable in a block scope appropriately in go.
package main
import (
"fmt"
"path/filepath"
)
func main() {
base := "/a/b/c"
other := "/a/b/c/d/e"
for base != other {
other, file := filepath.Split(other) // "other declared but not used"
fmt.Println(file)
}
}
I want to use both parts of filepath.Split, so I need :=, since file is not declared yet. I want other to get shorter and shorter, so I reassign the result of filepath.Split, but the go compiler doesn't let me run this code.
Why is this the case, and how am I supposed to do this sort of thing?

The body of the loop is an another block, so using short variable declaration in it will not use the other variable declared before, outside of the loop, but will create a new other variable scoped to the body block. Given that, this second other variable is not used anywhere, because the base != other condition in the loop refers to the outer other variable, hence the compile-time error.
Create the expected second variable first, and use simple assignment instead of short variable declaration:
base := "/a/b/c"
other := "/a/b/c/d/e"
for base != other {
var file string
other, file = filepath.Split(other) // "other declared but not used"
fmt.Println(file)
}
Note that the above code will run into an endless loop because filepath.Split() leaves the trailing slash in other, so in the next iteration filepath.Split() will return the same other (the last dir will not be cut off), and won't ever change again.
To make your code do what you want, you have to cut trailing slashes off, like this:
for base != other {
var file string
other, file = filepath.Split(other) // "other declared but not used"
fmt.Println(file)
if strings.HasSuffix(other, "/") {
other = other[:len(other)-1]
}
}
This will now run and output (try it on the Go Playground):
e
d
Note that the same thing could be achieved with a lot simpler code if you'd use filepath.Base() to get the last part of the path, and filepath.Dir() to get the parent folder, like this:
base := "/a/b/c"
other := "/a/b/c/d/e"
for base != other {
fmt.Println(filepath.Base(other))
other = filepath.Dir(other)
}
This outputs the same, try it on the Go Playground.
See related questions:
Why does initializing just one variable in a definition fail, in golang
Why there are two ways of declaring variables in Go, what's the difference and which to use?

Related

How to declare to variables of different datatypes in a single statement in Go [duplicate]

This question already has answers here:
Multiple variables of different types in one line in Go (without short variable declaration syntax)
(2 answers)
Closed 1 year ago.
I want to declare two variables of different datatypes(string and error) in a single statement in Go. I do not want to use the short declaration(:=) operator because I like specifying the type of the variable at declaration.
I am following a Go tutorial from the Go docs. I have a function called greetings.Hello() that I am calling from another module. The greetings.Hello() function looks like this:
package greetings
import (
"errors"
"fmt"
)
func Hello(name string) (string, error) {
// If no name was given, return an error with a message
if name == "" {
return "", errors.New("empty name")
}
// If a name was received, return a value
var message string = fmt.Sprintf("Welcome %v!", name)
return message, nil
}
So as you can see, this function returns two values(a string and an error). So ultimately, I would have to assign the result of this function to two variables in the caller. I am calling the greetings.Hello() function from a module named hello. The main function of the hello module's main package looks like this:
package main
import (
"fmt"
"log"
"creating_modules/greetings"
)
func main() {
log.SetPrefix("greetings: ")
log.SetFlags(0)
var message string, err error = greetings.Hello("")
if err !=nil {
log.Fatal(err)
}
fmt.Println(message)
}
The creating_modules/greetings is the greetings module that contains the function Hello(). Most of the gophers tackle it like this:
message, error := greetings.Hello()
But I want to declare the variables along with their datatypes in a single statement. Also the two variables should be assigned the return values of greetings.Hello(). The above mentioned main function of the hello module returns an error when it is run because of the incorrect assignment, referring to this line:
var message string, err error = greetings.Hello("")
The Go compiler returns this error when this code is run using go run:
.\hello.go:14:20: syntax error: unexpected comma at end of statement
This issue can simply be reproduced by copy-pasting the code above(note that the greetings module is a local module so you will need to set the reference path for go tools using go edit -replace)
Another thing to be noted is that my question is different from this question because that question is about declaring variables with the same data type in a single statement whereas mine is about declaring multiple variables with different data types in a single statement.
P.S i won't be surprised to know that Golang does not have this feature
declare the variables along with their datatypes in a single statement
Not possible
Supporting clause from the language spec under Variable declarations
If a type is present, each variable is given that type. Otherwise, each variable is given the type of the corresponding initialization value in the assignment. If that value is an untyped constant, it is first implicitly converted to its default type;
So something like below could work by not specifying either of the types, but you could very well use short variable declarations using := instead
var message, error = greetings.Hello()
But you can declare the variables explicitly with their type information and use the = assignment.
var message string
var err error
if message, err = greetings.Hello(""); err != nil {
log.Fatal(err)
}

Short format var declaration in for loop

Please see the code below. (A) is not ok in Go and I understand why. But why is (B) ok in Go ?
(A) is not ok: because re-definition of a (no new var in LHS of := )
(B) should be an error too: because re-definition of r (no new var in LHS of := ) as the loop will execute r := with each iteration while r is still in scope.
package main
import "fmt"
func main() {
a := make([]byte, 10)
fmt.Println(a)
a := make([]byte, 10) //not ok and I understand why : (A)
fmt.Println(a)
for i := 0; i < 5; i++ {
r := make([]byte, 10) //ok, but why is this ok? : (B)
fmt.Println(r)
}
}
The key idea behind the short format declaration is that it has to define at least one new variable in the current block. So it fails in the first case because it is attempting to redefine a in the current block without introducing any new variables. The second block works, because r is a new variable declared in the current block.
As describe in go doc specifications about Short_variable_declarations, it is temporary for the scope.
Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.
Short variable declarations may appear only inside functions. In some contexts such as the initializers for "if", "for", or "switch" statements, they can be used to declare local temporary variables.
You can not redeclare same variable with short variable declaration in same scope.
a, a := 1, 2 // illegal: double declaration of a or no new variable if a was declared elsewhere
In your case, r is in loop scope and each iteration r is a new variable. Because for loop repeats execution of a block.
A "for" statement specifies repeated execution of a block
If you need to clarify this please run below loop code and see r's memory addresses for each iterations. It will print five different addresses.
for i := 0; i < 5; i++ {
r := make([]byte, 10) //ok, but why is this ok? : (B)
fmt.Printf("%p\n",r)
}

Does fmt.Println have to be inside a function in Go?

Does fmt.Println need to always belong to a function?
Have used Python before and it allows it but on research, it seems that Java doesn't
fmt.Println("can I do it?")
Returns:
syntax error: non-declaration statement outside function body
It may be outside of a function, see this example:
var n, err = fmt.Println("I can do it")
func main() {
fmt.Println("In main(),", n, err)
}
It outputs (try it on the Go Playground):
I can do it
In main(), 12 <nil>
(The output values 12 <nil> are the values returned by the first fmt.Println() call, the number of bytes it has written and the error it returned which is nil indicating no error.)
Also note that you don't even have to store the return values of fmt.Prinln(), you can use the blank identifier like this:
var _, _ = fmt.Println("I can do it")
It cannot stand on its own at the top level "between" top-level declarations, but the above variable declaration (with blank identifier) pretty much achieves the same.
Spec: Source file organization:
Each source file consists of a package clause defining the package to which it belongs, followed by a possibly empty set of import declarations that declare packages whose contents it wishes to use, followed by a possibly empty set of declarations of functions, types, variables, and constants.
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
Obviously a package clause or import declaration can't contain an fmt.Println() call, and the top level declarations:
Declaration = ConstDecl | TypeDecl | VarDecl .
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
A constant declaration cannot contain an fmt.Println() call, that's not a constant expression. A type declaration also cannot contain function calls.
A variable declaration can, as shown in the example at the top of the answer.
Function and method declarations could also call fmt.Println(), but you were asking specifically if fmt.Println() can be called outside of them.
So the only place it is allowed outside of functions that is allowed at the top level is in variable declarations.
go always starts execution in the main function, so fmt.Println() need to be in the main function or a function that is called in main.

Redeclared variable unused

This code compiles:
func (wc *WordCounter) Write(buf []byte) (int, error) {
for adv, i := 0, 0; i < len(buf); i += adv {
adv, _, _ = bufio.ScanWords(buf[i:], true)
*wc++
}
return len(buf), nil
}
But the following does not compile. Notice the short declaration adv, token, _ := .. where I expected that adv would be redeclared (as opposed to be declared as a new var):
func (wc *WordCounter) Write(buf []byte) (int, error) {
for adv, i := 0, 0; i < len(buf); i += adv {
// error: adv declared and not used
adv, token, _ := bufio.ScanWords(buf[i:], true)
fmt.Println(string(token))
*wc++
}
return len(buf), nil
}
According to the Go spec:
a short variable declaration may redeclare variables provided they
were originally declared earlier in the same block. [...] Redeclaration does not introduce a new variable; it just assigns a new value to the original.
I guess this means that the for statement is a block in and of itself, and that adv therefore is considered to be declared over again (as opposed to being redeclared) in the for body?
This is working as intended. Consider the following:
https://play.golang.org/p/cyJZgM5QYn
package main
import (
"fmt"
)
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("%p", &i)
i := i
fmt.Printf(" | %p\n", &i)
}
}
Variables you declare within the for loop header are defined for the entire for loop. The first i printed in the above has the same address on every single iteration. On the other hand, variables declared inside the loop itself are local only to that iteration of the loop! Note that the second i printed on each line has a unique address, as a new variable is being created on each iteration. This makes the lifetime of the two variables, and thus their scopes, different. Since they exist in separate scopes, the inner scope can (usually inadvertently) shadow the outer scope variable via the short-form declaration.
I guess this means that the for statement is a block in and of itself,
and that adv therefore is considered to be declared over again (as
opposed to being redeclared) in the for body?
Yes, that's correct. Go specs say following about blocks:
A block is a possibly empty sequence of declarations and statements
within matching brace brackets.
Each "if", "for", and "switch" statement is considered to be in its own implicit block.
So you are declaring a new variable in your for-block with the same name as the previously declared variable. The new variable shadows the previously declared variable making it inaccessible from inside the for-block.
This is a common source of bugs if you don't realize there are now two variables with the same name and you think you are assigning values to the variable you declared in the outer block. go vet with the -shadow=true option can help find such bugs.

Idiomatic way to initialise an empty string in Go

In Go you can initialise an empty string (in a function) in the following ways:
var a string
var b = ""
c := ""
As far as I know, each statement is semantically identical. Is one of them more idiomatic than the others?
You should choose whichever makes the code clearer. If the zero value will actually be used (e.g. when you start with an empty string and concatenate others to it), it's probably clearest to use the := form. If you are just declaring the variable, and will be assigning it a different value later, use var.
var a string
It's not immediately visible that it's the empty string for someone not really fluent in go. Some might expect it's some null/nil value.
var b = ""
Would be the most explicit, means everyone sees that it's a variable containing the empty string.
b := ""
The := assigment is so common in go that it would be the most readable in my opinion. Unless it's outside of a function body of course.
There isn't a right way to declare empty variables, but there are some things to keep in mind, like you can't use the := shortcut outside of a function body, as they can with var:
var (
name string
age int64
)
I find var name = "" to be the clearest indication that you're declaring an empty variable, but I like the type explicitness of var name string. In any case, do consider where you are declaring variables. Sometimes all at once outside the function body is appropriate, but often nearest to where you actually use it is best.
rob (Pike?) wrote on a mailthread about top-level declaration
At the top level, var (or const or type or func) is necessary: each item must be introduced by a keyword for ur-syntactic reasons related to recognizing statement boundaries. With the later changes involving semicolons, it became possible, I believe, to eliminate the need for var in some cases, but since const, type, and func must remain, it's not a compelling argument.
There is a certain ambiguity in "short-variable" declarations (using :=), as to whether the variable is declared or redeclared as outlined in the specs:
Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-blank variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original.
There is absolutely no difference in the generated code (with the current compiler – Go 1.7.4), and also gometalinter does not complain for any of those. Use whichever you like.
Some differences:
You can only use the short variable declaration in functions.
With short variable declaration, you can create variables of multiple types in one line, e.g.
a, b := "", 0
The following 3 apps generate identical code:
a.go
package main
import "fmt"
func main() { var a string; fmt.Println(a) }
b.go
package main
import "fmt"
func main() { var a = ""; fmt.Println(a) }
c.go
package main
import "fmt"
func main() { a := ""; fmt.Println(a) }
To verify, build each (e.g. with go build -o a), and use diff on Linux to compare the executable binaries:
diff a b && diff a c
I try to stick to the short declaration for a couple of reasons.
It's shorter
Consistency
Allocates memory for maps, slices and pointers to structs and types.
Although var a string and a := "" are the same, b := []T{} and var b []T are not the same. When dealing with slices and maps the shorter declaration will not be nil. More often then not (especially with maps) I want allocated memory.
There are few situations where var will be needed, for instance, you are calling a function that will populate a property of a type.
var err error
foo.Name, err = getFooName()
Using := syntax in the above situation will error out since foo.Name is already declared.
just
*new(string)
It's only stuff in stackoverf related to empty strings in go. So it should be here

Resources