Chapter01 - pslpune/golang-jumpstart GitHub Wiki
Names of functions, variables, constant, types, statement labels, packages follow a simple rule. - name begins with a UTF-8 letter or an underscore and may have number of additional letters, digits and underscores. Also since Golang is case sensitive - heapSort and HeapSort
are entirely different names
Go has about 25 keywords
and those cannot be used as names when you program.
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
In addition to the keywords, Go also has predeclared names - these are not reserved and you can have these names but its strictly bound to context. Beware as this might also lead to a lot of confusion.
Constants: true, false, iota, nil
Types : int, int8, int32, int64
uint8, uint16, uint32, uint64,
float32, float64, complex128, complex64
bool, byte, rune, string, error
Functions: make, len, cap, new, append, copy, close, delete,
complex, real, imag, panic, recover
If a name appears inside the function/package its local(lifecycle) to that function, but can also be accessed from the outside. The way to protect it is by case of the first letter in the name. Example: const declared as delay
inside a package is private to the package and no other external package can see it. while the same constant declared as Delay
would mean external packages can access it.
Conventionally - Go prgrammers use short, very short names : though there is no limit as to how long the name could be. Its quite normal to have i
as the index of the loop and not index
Also Go programmers prefer camel case when forming names with 2 or more words. Also acronyms are all caps!
Preferred
- ✔️ QuoteRuneToASCII
- ✔️ quoteRuneToASCII
Not preferredooooo
- ❌ quote_rune_toASCII
- ❌ QuoteRuneToAscii
A declaration names an entity, and specifies some/all of its properties. There are 4 major kinds of declarations
- var
- const
- type
- func
Go program is stored in one / more .go
files. Each file begins with a package declaration that specifies which package is the file part of.
A var
declaration creates a variable of a particular type, attaches a name to it and sets its initial value.
var name type = expression
Either the type or the expression part of it can be omitted but not both. If the type is omitted the initializer expression can help identify the type while if the initializer expression is omitted then the variable would be assigned the respective zero
value
- incase of numerals = 0
- incase of strings = ""
- incase of interface and reference types = nil (slice, channel, map, function)
- incase of aggregates - struct,array it has all zero value of the elements
The "zero" vaue mechanism is very helpful in that it assigns a well defined value to any variable the moment it is initialized, and that there isnt anything called as
undefined
variable in Go
Its possible to declare multiple variables in a single statment when there are more than one variable that needs intialization.
var name, email, location string // name = "" ,email = "" , location =""
var delay, ok, name = 3.5, true, "niranjan"
Only within the functions there is what is called as the short hand of the above variable declaration. Type of the variable here is omitted and inferred from the assignment expression
freq := rand.Float() * 3.0
t := 0.0
As you can notice this a popular choice of declaring local variables with most of the Go programmers. Short declaration also applies to multi-declaratio
i,j := 0,3
Short hand declarations can also be used when calling functions
f, err := os.Open("path/to/file", 667)
A subtle but important point, that short hand declaration does not necessarily always declare new variables on the left side. If the variable with the same name is already declared within the same lexical scope, then it only assigns.
f, err := os.Open("path/to/file", 667)
f2, err := os.Create("path/to/someother/file")
As you can see, err here was declared on the previous line, but in the following one it actually only assigns the value to the err
variable. There is a caveat though in this. Following code would not compile.
f, err := os.Open("path/to/file", 667)
f, err := os.Create("path/to/someother/file")
Atleast one variable on the left should be a new declaration - compiler will flag this an error. Correct way is as below.
f, err := os.Open("path/to/file", 667)
f, err = os.Create("path/to/someother/file") // pure assignment statement
Any function / procedure has 3 components, - signature, body, call. Here since we are discussing declarations we focus on signature. A signature has
- Name of the function
- Parameters it takes in
- return values tha it spits out
func DoThis(param1 string, param2 int, flag bool) (int, error){
}
As you can notice, in Go there can be multiple return values. The func
keyword is important. Also Go has made functions as first class citizens and that they can passed into other function as params.
func SummTop(a,b) int {
// summation of number from b-a but the first 2 values
}
func SummBottom(a,b) int {
// Summation of number from b-a but last 2 values
}
func Amplify(a,b int Summation func(a,b int)int) int {
return 4*Summation(a,b)
}
func main(){
Amplify(3,10, SummBottom) // almost a polymorphic behaviour
}
As you can notice this lets you keep the context where it is and move around an entire function along just like it was polymorphic. Whats interesting in Go is that you can have functions with variable number of arguments calling it. When specifying about the params we indicate variable params as so
func Concatenate(texts ...string) string {
for _, s := range texts ...{
}
}
Return types of the functions can be named in Go. This saves us declaring the variables for return inside the function.
func SomeCalculation(a, b float) (result int, e error){
result = a+b
e =nil
return result, e
}
Type of the variable signifies
- Data type and the characteristics of the data type (size in the memory) it would be taking on
- How they are represented internall in the memory
- Intrinsic operations that can be performed on them
In any program there are variables that have the same representation but signify different concecpts. For instance an int
represents an Integer but it can be a loop index, timestamp, file descriptor, or month. A string
could be a name, email, color, or location.
A type declaration defines a new named type that has the same underlying type as an existing type. Such that they cannot be unintentionally mixed and used for the same purpose.
import "fmt"
// 2 types of floats, same underlying types but different domain representations
// this is it avoid mixing and interchangeably using the value without conversion
type Celcius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celcius = -273.15
FreezingC Celcius = 0.0
BoilingC Celcius = 100.0
)
func CToF(c Celcius) Fahrenheit {
return Fahrenheit(c *9/5+32)
}
func FtoC(f Fahrenheit) Celcius {
return Celcius((f-32)*5/9)
}
Celcius
and Fahrenheit
have the same underlying type but they cannot be compared in an arithmetic expression. Distinguishing the types on their domain representaion makes it possible to avoid inadvertently combining 2 values of temperatures on different scales. If you had to force convert the values - disregarding the scales of temperature you would have to make an explicit conversion
t := Celcius(0.0)
f = Fahrenheit(t) // you made your intentions clear here, you want a forced conversion disregarding the temp scales.
The value above would be now 0 deg Fahrenheit, perhaps not was intended in real world, but since you have made an explicit conversion its assumed thats what you intend.
Packages serve as the same purpose as the libraries / modules in other languages. - supporting
- Modularity
- Re-Use
- Encapsulation
- Separate compilation
Source code in a package resides in one / more .go
files usually in a directory whose name resembles the import path. Creating a new package under a module is as simple as creating a directory under a module and has atleast one go file with package <name>
In directory tempconv we make this go file & begin by specifying package name tempconv
. For all future references our package can be referred by anyone on the internet as "module/path/package_name". So for example If I have a module registered as github.com/kneerunjun/services
and the below package can be publicly referred to as github.com/kneerunjun/services/tempconv
package tempconv
import "fmt"
type Celcius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celcius = -273.15
FreezingC Celcius = 0.0
BoilingC Celcius = 100.0
)
func CToF(c Celcius) Fahrenheit {
return Fahrenheit(c *9/5+32)
}
func FtoC(f Fahrenheit) Celcius {
return Celcius((f-32)*5/9)
}
Package initilization begins by initializing the package level variables in order which they are declared, except that the dependencies are resolved first. If the package has multiple go files they are compiled in a sequence sorted by the go tool.
Every package then also has the init
function which isnt actually callable but is called internall whenever the package is being initialized. Typical tasks that can be done inside the init
function are
- log setup , config
- reading environment variables
- testing database singular connections
Declaration associates a name with a programming entity, plus where its declared also has a lot of significance. A syntatic block of code is that refers to a code that is surrounded by the curly braces. - function or loop. A name declared inside such a block is not visible outside the block. The block encloses the declarations, and determines their scope.
Braced blocks together with other blocks that are also encapsulating a scope but havent got any braces surrounding are referred to as what is called lexical scope
A program often contains multiple declarations of the same name (but not inside the same lexcical scope). Here to determine the preceedence the compiler will prefer the innermost scope (cascaded scope) and the declaration that is innermost will take preceedence when execution in that scope.
func main(){
x:= "hello!"
for i :=0; i< len(x);i++{
x := x[i]
if x != '!' {
x := x + 'A'- 'a'
fmt.Printf("%c", x) // "HELLO" one character at a time
}
}
}
Lets pick the distinct lexical scopes in the above snippet
This one is the top local scope under the main function. The x
declared here belongs to this scope
func main(){
x := "hello"
}
The for loop creates another one, x declared here (left side of the := operator) belongs to this scope,while the x appearing in len(x)
and x[i]
are from the previous scope. - we shall come to this a bit later but for now keep this in mind.
for i :=0; i< len(x);i++{
x := x[i]
}
The if condition then creates the last cascaded one. here x + 'A'- 'a'
x in the expression is from the previous scope as well as the x
in x != '!'
if x != '!' {
x := x + 'A'- 'a'
fmt.Printf("%c", x) // "HELLO" one character at a time
}
For all practical and general purposes it is very unlikely you would name your variables identical thru cascaded scopes, but this example is to show you how scopes influence the value of the variables and which ones are the referring to.
var cwd string
func init (){
cwd, err := os.Getwd()
if err !=nil{
log.Fatalf("error getting the working directory %s", err)
}
log.Printf("current working directory %s", cwd)
}
Compiler will complain (cwd is declared but not used), with a red squiggly line under cwd
on the outer scope. Isn't the cwd
inside init
being used ?
:=
operator would redeclare a new variable local to its scope again with the same name. If you really want to assign cwd, and not declare a new variable you would have to get away from the :=
operator
var cwd string
func init (){
var err error
cwd, err = os.Getwd()
if err !=nil{
log.Fatalf("error getting the working directory %s", err)
}
log.Printf("current working directory %s", cwd)
}
With the =
operator err has no valid declaration, and hence to ger around the problem on a separate line we have independently declared err as error
Along with the lecturer we are going to setup the demo project step by step. At the end of it we would have a simple Http JSON API endpoint running on GO This would require you have a environment setup locally or on one of the playgrounds. Alternatively we also encourage you to start using github codedspaces.