What does this code snippet do?
var i int
_ = i
I understand the use of "_" as a blank identifier, but what does the second line in the above achieve?
Here is an example from the etcd GitHub repository: etcd
The code is machine generated. The generator added the statements _ = i to avoid unused variable declarations in the case where there's nothing to marshal.
The author of the code generator probably found it easier to add the blank assignment statements than to omit the variables when not needed.
I would guess you might do this to stop go complaining about an unused variable
It would be better to not declare the variable at all
Note, sometimes the underscore is used in imports so that the init() code of a package will get executed, however there is no need to call functions in that package.
This is often a technique applied for image processing to register image handlers.
See
A use case for importing with blank identifier in golang
Related
I've been reading some Golang code from github.com/lib/pq which provides drivers for interacting with a postgres database.
Among the code I came across this:
go func() {
select {
case <-done:
_ = cn.cancel()
finished <- struct{}{}
case <-finished:
}
}()
The cancel function looks like:
func (cn *conn) cancel() error
As far as I can tell, the underscore isn't being used as a static assertion about a type (and therefore the compiler doesn't evaluate any side effects as far as I can see (as in this example)) and it isn't a second parameter whereby the author may want to discard it.
In summary: Why assign the result of the cancel function (the error) to an underscore?
Code must be correct. To be sure that code is correct, code must be readable.
The First Rule of Go: Check for errors.
func (cn *conn) cancel() error
If I write
cn.cancel()
did I forget to check for errors or did I decide to discard the error value?
However, if I write
_ = cn.cancel()
I did not forget to check for errors and I did decide to discard the error value.
The Go Programming Language Specification
Blank identifier
The blank identifier is represented by the underscore character _. It
serves as an anonymous placeholder instead of a regular (non-blank)
identifier and has special meaning in declarations, as an operand, and
in assignments.
Assignments
The blank identifier provides a way to ignore right-hand side values
in an assignment:
The blank identifier “_” is a special anonymous identifier. When used in an assignment, like this case, it provides a way to explicitly ignore right-hand side values. So, the developer has decided to ignore/discard the error returned from this method call.
A few reasons why they may have done this (based on a quick glance at the method call and context, my guess is 3 or 4):
The method call is guaranteed to succeed in this context.
The error is already handled sufficiently within the method call; no reason to handle it again.
The error doesn’t matter (eg the relevant process is going to end anyway and the outcome will be the same as if the method call has succeeded without error).
The developer was in a hurry to get something working, ignored the error to save time, then failed to come back and handle the error.
For code append(slice1, 1), Go compile will give error "append(...) evaluated but not used". And we have to use like slice1 = append(slice1,1) because append doesn't modify slice1 and it will return a new slice.
I think it is a good hint since this will prevent lots of bug since we often didn't know function like append will change original array or not. In JavaScript array1.push('item') will change array1 in place and return new length of the array.
I want to utilize this kind of code checking:
func appendStr(str string, tail string) string {
b := str + tail
return b
}
a := "a"
appendStr(a, "b")
But Go compiler didn't give error. So compiler do some special checking on append method? Since Go pass parameter by value, Compiler should know appendStr has no change to modify pass-in parameter.
append() is special because it's a built-in function, and the compiler does extra check on it. It is very rarely useful to not use the return value of append(), so the Go authors decided to make it a compile-time error if it is not used.
On the other hand, calling "ordinary" functions which have return values often have "side effects", and it's common to just call a function for its "side effects" and not use its return values. A very common example is fmt.Println(): you often print something to the console, and you rarely (if ever) check if that succeeds or how many bytes were actually written.
The language spec allows you to not use the return values of functions, so it's valid to do so and you can't force the compiler to make an error or warning out of it, you can't make the compiler to "mark" valid code with error.
See related question: Return map like 'ok' in Golang on normal functions
The way this is typically done in Go is by using an extra tool, a linter if you will. go vet is commonly used to point out things in the code that "don't look right" and which are probably bugs. It seems that you cannot make it check your own functions out-of-the-box, only things like fmt.Sprintf will be checked.
Another tool is errcheck which reports ignored errors.
You could fork one of these tools and insert your own function(s) there, then make everyone check their code before committing it to source control or check it automatically.
With syntax highlighting enabled, it's distracting while reading code like answer to this question with new used as a variable name.
I'm trying to think of a reason why only a subset of keywords would be reserved and can't come up with a good one.
Edit: Alternate title for this question:
Why are Go's predeclared identifiers not reserved ?
That's because new and make aren't really keywords, but built-in functions.
If you examine the full list of the reserved keywords, you won't see len or cap either...
In short: because using predeclared identifiers in your own declarations don't make your code ambiguous.
new and make are builtin functions, amongst many others. See the full list in the doc of the builtin package.
Keywords are a carefully chosen small set of reserved words. You cannot use keywords as identifiers because that could make interpreting your source ambiguous.
Builtin functions are special functions: you can use them without any imports, and they may have varying parameter and return list. You are allowed to declare functions or variables with the names of the builtin functions because your code will still remain unambiguous: your identifier in scope will "win".
If you would use a keyword as an identifier, you couldn't reach your entity because an attempt to refer to it by its name would always mean the keyword and not your identifier.
See this dirty example of using the predeclared identifiers of the builtin function make and the builtin type int (not recommended for production code) (try it on the Go Playground):
make := func() string {
return "hijacked"
}
int := make() // Completely OK, variable 'int' will be a string
fmt.Println(int) // Prints "hijacked"
If you could use keywords as identifiers, even interpreting declarations would cause headache to the compiler: what would the following mean?
func() - is it calling your function named func or is it a function type with no params and no return types?
import() - is it a grouped import declaration (and importing nothing) or calling our function named import?
...
I've been writing a test program to assist in learning Go. While doing so, I've encountered a few instances where I thought the compiler should have detected an error. I've encountered another similar situation, so I thought I should ask why this situation is not treated as an error.
Example situation :
if oError = rwfile.WriteLines(asParams, sParamsFilename); oError != nil {
fmt.Printf("Error on write to file Params. Error = %s\n", oError)
} else {
println("Params file write OK")
}
In the example above, whether or not the variable "oError" is declared, the compiler does not indicate an error. It also works if the line contains the following when the variable is not declared (as expected):
if oError := rwfile.WriteLines(asParams, sParamsFilename); oError != nil {
If I declare the variable "oError", then ":=" does not work (as expected).
"rwfile" is a package that I have written, and the function in question starts as follows:
func WriteLines(asBuff []string, sFilename string) error { // write text file
If I create an error with the write of the file, and use "=" without declaring the variable "oError", the program works correctly and detects the non-nil "oError" variable.
So, why is use of "=" in the above not treated as an error when oError is not declared a variable?
The Go version is go1.1.2 Windows/386.
This means that you have an variable named oError elsewhere in the package. (Note that this variable need not be in the same file; it could be in a different file with the same package clause.) So, when you use oError = ..., you're assigning a value to that package variable, and when you use oError := ..., you're declaring a local variable that hides the package variable. (Per the specification, "An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the entity declared by the inner declaration." [link])
If you try a different identifier, a unique one, you should see that the compiler indeed complains.
It's unrelated to the actual question, but `go fmt` can really help with tracking errant stuff in a big program.
Also, following the style guidelines really helps: use terse code styles (no hungarian variable names! took me ages to get used to short names) and short files: a 2Kloc file is probably too big. It's massively worth taking a wander through the standard library source code to see what good Go code looks like
When creating a tracepoint in Visual Studio (right-click the breakpoint and choose "When Hit..."), the dialog has this text, emphasis mine:
You can include the value of a variable or other expression in the message by placing it in curly braces...
What expressions are allowed?
Microsoft's documentation is rather sparse on the exact details of what is and is not allowed. Most of the below was found by trial and error in the Immediate window. Note that this list is for C++, as that's what I code in. I believe in C#, some of the prohibited items below are actually allowed.
Most basic expressions can be evaluated, including casting, setting variables, and calling functions.
General Restrictions
Only C-style casts supported; no static_cast, dynamic_cast, reinterpret_cast, const_cast
Can't declare new variables or create objects
Can't use overloaded operators
Ternary operator doesn't work
Can't use the comma operator because Visual Studio uses it to format the result of the expression; use multiple sets of braces for multiple expressions
Function Calls
Prohibited calls:
Lambdas (can't define or call them)
Functions in an anonymous namespace
Functions that take objects by value (because you can't create objects)
Permitted calls:
Member functions, both regular and virtual
Functions taking references or pointers, to either fundamental or class types
Passing in-scope variables
Using "&" to pass pointers to in-scope variables
Passing the literals "true", "false", numbers
Passing string literals, as long you don't run afoul of the "can't create objects" rule
Calling multiple functions with one tracepoint by using multiple sets of braces
Variable Assignment
Prohibited:
Objects
String literals
Permitted:
Variables with fundamental types, value either from literals or other variables
Memory addresses, after casting: { *(bool*)(0x1234) = true }
Registers: { #eip = 0x1234 }
Use Cases
Calling functions from tracepoints can be quite powerful. You can get around most of the restrictions listed above with a carefully set up function and the right call. Here are some more specific ideas.
Force an if
Pretty straightforward: set up a tracepoint to set a variable and force an if-condition to true or false, depending on what you need to test. All without adding code or leaving the debug session.
Breakpoint "toggling"
I've seen the question a few times, "I need to break in a spot that gets hit a lot. I'd like to simply enable that breakpoint from another breakpoint, so the one I care about only gets breaks from a certain code path. How can I do that?" With our knowledge above, it's easy, although you do need a helper variable.
Create a global boolean, set to false.
Create a breakpoint at your final destination, with a condition to break only when the global flag is true.
Set tracepoints in the critical spots that assign the global flag to true.
The nice thing is that you can move the tracepoints around without leaving the debugging session. Use the Immediate window or the Watch window to reset your global flag, if you need to make another run at it. When you're done, all you need to clean up is that global boolean. No other code to remove.
Automatically skip code
The EIP register (at least on x86) is the instruction pointer. If you assign to it, you can change your program flow.
Find the address of the line you want to skip to by breaking on it once and looking at the value of EIP, either in the Registers window or the Watch window with "#eip,x". (Note that the value in the Registers window is hex, but without the leading "0x".)
Add a tracepoint on the line you want to skip from, with an expression like {#eip = address}, using the address from step 1.
EIP assignment will happen before anything on the line is executed.
Although this can be handy, be careful because skipping code like this can cause weird behavior.
As Kurt Hutchinson says, string assignment is not allowed in a tracepoint. You can get around this by creating a method that assigns the string variable, and call that.
public static class Helper
{
public static void AssignTo(this string value, out string variable)
{
variable = value;
}
}
Then in your tracepoint message:
{"new string value".AssignTo(out stringVariable)}