I'm trying to figure out simple switch in Golang and I'm stuck with variable scope.
var body string
var errs error
req := gorequest.New()
var resp gorequest.Response
switch verb {
case 0:
resp, body, errs := req.Get(suburl).
Set("X-Auth-Token", d.Token).
Set("Content-type", "application/json").
End()
}
if errs != nil {
return &ConnResponse{resp.StatusCode, body, fmt.Errorf("%s", errs)}
}
I've declared resp, body, errs and req before switch and I've thought that they will be available after the switch body.
What compiler returns is below errors (from the case declaration)
# command-line-arguments
./conn.go:135:3: resp declared and not used
./conn.go:135:9: body declared and not used
./conn.go:135:15: errs declared and not used
So I'm curious is the variable scope inside switch body is somehow different from declared in function? How would this piece of code look like to be able to get access to data after switch body.
Your issue is in this line:
resp, body, errs := req.Get(suburl)
The short variable declaration operator := creates new variables and assigns values to them. These new variables are said to "shadow" the variables you created in the outer scope, because they have the same names and thus they "hide" the outer-scoped variables from within that scope. To fix the issue, just assign the values to your existing variables from the outer scope, instead of creating new ones:
resp, body, errs = req.Get(suburl)
Note the use here of assignment = instead of short declaration :=.
Related
I am trying to upload bunch of files using the company's api to the storage service they provide. (basically to my account). I have got lots of files like 40-50 or something.
I got the full path of the files and utilize the os.Open, so that, I can pass the io.Reader. I did try to use client.Files.Upload() without goroutines but it took so much time to upload them and decided to use goroutines. Here the implementation that I tried. When I run the program it just uploads one file which is the one that has the lowest size or something that it waits for a long time. What is wrong with it? Is it not like every time for loops run it creates a goroutine continue its cycle and creates for every file? How to make it as fast as possible with goroutines?
var filePaths []string
var wg sync.WaitGroup
// fills the string of slice with fullpath of files.
func fill() {
filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
filePaths = append(filePaths, path)
}
if err != nil {
fmt.Println("ERROR:", err)
}
return nil
})
}
func main() {
fill()
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
for _, path := range filePaths {
wg.Add(1)
go func() {
defer wg.Done()
f, err := os.Open(path)
if err != nil {
log.Println("err:OPEN", err)
}
upload, err := client.Files.Upload(context.TODO(), f, path, 0)
if err != nil {
log.Println("error uploading file:", err)
}
fmt.Println(upload)
}()
}
wg.Wait()
}
Consider a worker pool pattern like this: https://go.dev/play/p/p6SErj3L6Yc
In this example application, I've taken out the API call and just list the file names. That makes it work on the playground.
A fixed number of worker goroutines are started. We'll use a channel to distribute their work and we'll close the channel to communicate the end of the work. This number could be 1 or 1000 routines, or more. The number should be chosen based on how many concurrent API operations your putio API can reasonably be expected to support.
paths is a chan string we'll use for this purpose.
workers range over paths channel to receive new file paths to upload
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
)
func main() {
paths := make(chan string)
var wg = new(sync.WaitGroup)
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(paths, wg)
}
if err := filepath.Walk("/usr", func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Failed to walk directory: %T %w", err, err)
}
if info.IsDir() {
return nil
}
paths <- path
return nil
}); err != nil {
panic(fmt.Errorf("failed Walk: %w", err))
}
close(paths)
wg.Wait()
}
func worker(paths <-chan string, wg *sync.WaitGroup) {
defer wg.Done()
for path := range paths {
// do upload.
fmt.Println(path)
}
}
This pattern can handle an indefinitely large amount of files without having to load the entire list in memory before processing it. As you can see, this doesn't make the code more complicated - actually, it's simpler.
When I run the program it just uploads one file which is the one
Function literals inherit the scope in which they were defined. This is why our code only listed one path - the path variable scope in the for loop was shared to each go routine, so when that variable changed, all routines picked up the change.
Avoid function literals unless you actually want to inherit scope. Functions defined at the global scope don't inherit any scope, and you must pass all relevant variables to those functions instead. This is a good thing - it makes the functions more straightforward to understand and makes variable "ownership" transitions more explicit.
An appropriate case to use a function literal could be for the os.Walk parameter; its arguments are defined by os.Walk so definition scope is one way to access other values - such as paths channel, in our case.
Speaking of scope, global variables should be avoided unless their scope of usage is truly global. Prefer passing variables between functions to sharing global variables. Again, this makes variable ownership explicit and makes it easy to understand which functions do and don't access which variables. Neither your wait group nor your filePaths have any cause to be global.
f, err := os.Open(path)
Don't forget to close any files you open. When you're dealing with 40 or 50 files, letting all those open file handles pile up until the program ends isn't so bad, but it's a time bomb in your program that will go off when the number of files exceeds the ulimit of allowed open files. Because the function execution greatly exceeds the part where the file needs to be open, defer doesn't make sense in this case. I would use an explicit f.Close() after uploading the file.
I have the following function:
override fun countForTicket(dbc: SQLiteDatabase, ticketId: Long): Int {
var ret: Int
dbc.query(
TABLE_SECOND_CHANCE_PRIZES, arrayOf("count(id)"),
"ticket = ?", arrayOf(ticketId.toString()),
null, null, null
).use { c ->
ret = if (c.moveToFirst()) {
c.getInt(0)
} else {
0
}
}
return ret
}
The problem is that in line return ret ret is underlined with red and when trying to compile it gives me error:
Variable 'ret' must be initialized
From my point of view it seems that ret is always initialized. What am I missing?
Is it because the initialization is happening in a lambda and the compiler cannot guarantee that the variable is initialized?
The compiler isn't smart enough to know for sure the lambda will be run once, so it can't figure this out for you.
The reason we don't have this problem with many of the standard library higher-order functions is that they utilize contracts, which tell the compiler what they are doing with the lambda that is passed in (such as guaranteeing that the lambda will be called exactly once).
Unfortunately, Closeable.use() doesn't specify a contract (possibly because of it re-throwing exceptions?).
But use does return the result of calling the lambda, so you could do
val ret = dbc.query(...).use { c ->
if (c.moveToFirst()) {
c.getInt(0)
} else {
0
}
}
The compiler don't allow unsafe variables like that to be returned. A variable must always be something.
In your case, ret is initialized inside a lambda. The compiler doesn't know if this lambda is executed or not. If not, ret remains in its unsafe state. Throwing a NullPointerException at the end.
If you're sure that this variable is always assigned you can look at lateinit variables. You can also put a default value to it var ret = 0 and ommit the else statement.
kotlin/jvm jdk8 not support code
var buffer = StringBuffer();
var arr = arrayOf("1","2","3","4");
arr.forEach {buffer::append}
error info
java jdk8 supported code,
String[] b = new String[]{"c", "b"};
Arrays.stream(b).forEach(buffer::append);
In general, If you want to pass a method reference (buffer::append) to a method that takes a lambda, you need to enclose it in parentheses, not curly braces. In this specific case, you can't pass the append method as a method reference, because it returns StringBuilder, and forEach requires a method that returns Unit.
To make your code work, use a lambda:
arr.forEach { buffer.append(it) }
You can't copy-paste and use Stream code like that, because it ends up parsing it wrong. Since foreach blocks also contain the data, you can just do:
var buffer = StringBuffer();
var arr = arrayOf("1","2","3","4");
arr.forEach{buffer.append(it)}
The forEach block doesn't take a method as an argument, so doing buffer::append doesn't add any data, since you have to do that yourself
I see following code (I simplified it a bit).
func getEndpoints(db *sqlx.DB) s.Endpoints {
var endpoints s.Endpoints
{
repository := acl.NewRepository(db)
service := stat.NewService(repository)
endpoints = s.Endpoints{
GetEndpoint: s.MakeEndpoint(service),
}
}
return endpoints
}
If I understand this code correctly, code inside var endpoints s.Endpoints{...} is executed line by line and endpoints = s.Endpoints ... line initialises var endpoints variable declared above.
I suppose that it's correct to rewrite it like this (correct me if I'm wrong):
func getEndpoints(db *sqlx.DB) s.Endpoints {
repository := acl.NewRepository(db)
service := stat.NewService(repository)
endpoints := s.Endpoints{
GetEndpoint: s.MakeEndpoint(service),
}
return endpoints
}
So can somebody explain me why initialisation is written inside var endpoints s.Endpoints{...}. Is there any idea to do it like this? Am I missing something?
Adding a new block will create a new variable scope, and variables declared in that block will not be available outside of it:
var endpoints s.Endpoints
{
repository := acl.NewRepository(db)
service := stat.NewService(repository)
endpoints = s.Endpoints{
GetEndpoint: s.MakeEndpoint(service),
}
}
// service and repository variables are not defined here!
In your specific simplified example it makes little sense, but if you have other blocks with the same variables it makes more sense. For example:
var endpoints s.Endpoints
{
repository := acl.NewRepository(db)
service := stat.NewService(repository)
endpoints = s.Endpoints{
GetEndpoint: s.MakeEndpoint(service),
}
}
// Get another repository
{
repository := otherRepo.NewRepository(db)
repository.DoSomething()
}
Some people consider this "good hygiene". Personally, I don't think it's worth the decrease in readability.
They are equivalent.
The {...} block has nothing to do with the variable declaration with the var keyword. It just happens to be written one after the other.
The {...} is a simple block, nothing else. The var declaration does not require that block, and even if there is one, it is not related to the variable declaration. You can insert a block wherever you would insert a statement.
The rare case when an explicit block is used (when there isn't required one) is to group statements, and to control the scope of the variables and other identifiers declared inside them, because the scope of the variables end at the end of the innermost containing block (Spec: Declarations and Scope).
The following code works correctly - output: You chose Test 1
package main
import (
"fmt"
)
type TNameMap map[int]string
var nameMap TNameMap
func init() {
nameMap = make(TNameMap)
nameMap[1] = "You chose Test 1"
nameMap[2] = "You chose Test 2"
nameMap[3] = "You chose Test 3"
}
func main() {
fmt.Println(nameMap[1])
}
If I comment out the first line in init() i.e //nameMap = make(TNameMap) , I get a panic when main() runs, because nameMap was never initialized:
panic: runtime error: assignment to entry in nil map
But - if in init() I write nameMap := make(TNameMap)
instead of nameMap = make(TNameMap) , I get no panic, but also no output - main() simply runs and process terminates.
I understand that if I use the Initialization operator - nameMap := make(TNameMap) - I have declared a new variable nameMap that is scoped only to the init() function and so only the package level variable var nameMap TNameMap is in scope for main(), resulting in no output, because the package level var holds no map data.
But, I am confused: Why don't I get the panic in that situation? If main() is making the call on the package var, it was never initialized - so why no panic?
According to the Go spec:
A nil map is equivalent to an empty map except that no elements may be
added.
This means that you can read from a nil map, but not write. Just like the panic says "assignment to entry in nil map". If you comment out just the line nameMap = make(TNameMap) it will crash because you attempt to write to it in init (which is where the panic happens). If you comment out the entirety of init the Println will not crash because you're permitted to access (read from) a nil map.
Changing the assignment to a declaration is just masking the real issue here, what's happening is it's making all the assignments valid, and then discarding the result. As long as you make the assignments valid (either by removing them or making a temporary variable), then you will observe the same behavior in Println.
The value returned by a nil map is always the zero value of the value type of the map. So a map[T]string returns "", a map[T]int returns 0, and so on. (Of course, if you check with val,ok := nilMap[key] then ok will be false).