CSC/ECE 517 Fall 2014/ch1a 10 zz
Go Programming language by Google is an open source, statically typed programming language developed at Google. It is derived from C language but has features for garbage collection, type safety and some dynamic-typing capabilities, additional built-in types such as variable-length arrays and key-value maps, and a large standard library. Many companies including Google, Ebay, Dropbox, etc use Go. Go’s open source libraries include Go’s standard library for basic functionality and third party libraries with more specialized tools.
Background
Go Programming language, also known as golang, was first an experimental project started by Ken Thompson, Rob Pike, and Robert Griesemer in late 2007. It started off as pure research and further developed into a real programming language. As computing technology progressed, there were problem introduced by multicore processors, networked systems, massive computation clusters, and the web programming models. The problems were being worked around on instead of being facing them head-on. Also, the scale of programming changed. There are tens of millions of lines of codes that are worked by thousands of programmers, which is updated every day. And this naturally lead to increased build time that can take up upto couple hours.
Go was designed to solve these problems and make this environment more productive with not only built-in concurrency and garbage collection, but also rigorous dependency management, the adaptability of software architecture as systems grow, and robustness across the boundaries between components. In summary, Go is a compiled, concurrent, garbage-collected, statically typed language developed at Google. It is efficient, scalable, and productive.
Examples
Hello world
package main import "fmt" func main() { fmt.Println("Hello, 世界") }
Array
func Sum(a *[3]float64) (sum float64) { for _, v := range *a { sum += v } return } array := [...]float64{7.0, 8.5, 9.1} x := Sum(&array) // Note the explicit address-of operator
Append
func append(slice []T, elements ...T) []T x := []int{1,2,3} x = append(x, 4, 5, 6) fmt.Println(x) x := []int{1,2,3} y := []int{4,5,6} x = append(x, y...) fmt.Println(x)
Interfaces
type Sequence []int // Methods required by sort.Interface. func (s Sequence) Len() int { return len(s) } func (s Sequence) Less(i, j int) bool { return s[i] < s[j] } func (s Sequence) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // Method for printing - sorts the elements before printing. func (s Sequence) String() string { sort.Sort(s) str := "[" for i, elem := range s { if i > 0 { str += " " } str += fmt.Sprint(elem) } return str + "]" }
Goroutines
go list.Sort() // run list.Sort concurrently; don't wait for it. func Announce(message string, delay time.Duration) { go func() { time.Sleep(delay) fmt.Println(message) }() // Note the parentheses - must call the function. }
Narration
Go was built with an idea to improve C++. As a result , the syntax mirrors that of C to a great extent. The changes which are present though, are all aimed at making the code more readable and concise.Type safety is one the most important features of Go. The entire language is built around three facets - conciseness, simplicity and safety. The syntax is structured with patterns which are used in dynamic programming languages. Type inference is one of these features. When a variable is defined, Go does not require the programmer to specify a data-type. The data type is inferred from the value specified by the programmer.
num := 0 and not int num := 0
Go provides fast compilation times and remote package management. There is also a lot of support in the form on online documentation for the packages. There are built-in concurrency primitives. Go supports multitasking by providing light-weight processes. These run in user space on top of a single kernel thread and these threads share address space with other light weight threads within the same process. Interprocess communication is supported by use of channels , which are models for interprocess communication and synchronization. Go has an interface system instead of virtual inheritance. It also supports type embedding. Go has toolchain which produces statically linked native binaries without external dependencies. The whole objective is to make the syntax simple enough to remember for a programmer. In lieu of this , Go omits certain features which are common to other programming languages:
- Type Inheritance
- Method and/or Operator Overloading
- Circular Dependencies around Packages
- Pointer Arithmetic
- Assertions
- Generic Programming
Dependencies in Go
One of the most important features of Go is that it is scalable. Hence, a lot of its internal workings are geared towards that objective. One example is that when a program imports a package that it does not use, it will not compile. The compiler decides itself that unused dependencies are compile time errors. Thus, the dependency tree for a Go program is always minimal. There are no extra edges or branches. Thus, the time for compilation and build is reduced because no extra code is being compiled. Go has a very efficient method of handling transitive dependencies too. For instance, suppose a package A imports a package B. Package B imports a package C. But, package A does not import package C. That would mean A has a transitive dependency on C through B. Although A may not be using any identifiers or methods which belong to C directly in its source code , it may be using identifiers from B which reference code in C. A good example is suppose A references a formatted I/O implementation from B which references a buffered I/O implementation from C , although A does not directly reference the buffered I/O implementation. To build , the order of compilation is C,B,A respectively. In order to compile A, the compiler only reads the object file created by B’s compilation , not the source code. The object file contains all the necessary information for the compiler to build the dependencies for A. This is an important design feature because it means that every import causes the opening of exactly one object file. This saves a lot of compiler time spent in opening multiple files to build various dependencies. To make the compiler even more fast, the object file is arranged in a way which places the export data in the beginning of the file, so that the compiler can stop reading it as soon as the export data is read. This novel approach to dependency management is what makes Go faster at compilation times than C or C++. Another optimization is that the programmer places the export data in the object file and not a second file in conjunction to the actual source code. This also means that the export data can never go out of sync with the source code itself. A second file would create the risk of the export data going out of date. The dependency graphs in Go have no cycles. The compiler and linker both check to ensure that there are no cyclic imports. This keeps the tree clean and and makes sure that there is a clear demarcation between the packages.
Packages
Go’s packages are designed with an architecture which combines the features of libraries , namespaces and modules. To use a package, the path of the package is specified in the import clause. This package name is now used to identify unique items in the source code. Package paths need to be unique but there is no need for package names to be unique. In fact , Go programmers are encouraged to use simple and short package names in order to make source code more readable. For instance , there are many ‘server’ packages in google’s code base. Go provides support for remote packages too because the package path is just an arbitrary unique string. We can just use this path to reference a url from where the remote package can be used.
Semantics
The semantics of Go is very similar to C in the sense that it is statically typed , procedural and has pointers. This makes it easy for programmers who have worked with C or C++ to learn Go easily. Even programmers with knowledge of Java , Javascript do not find Go difficult to learn. Having said that , Go makes many small changes to C syntax , mainly to make it more robust.
- Go has no pointer arithmetic
- There are no implicit numeric conversions
- Array bounds are always verified ( more robust )
- There are no type aliases
- There are no post fix and pre fix incremental operators.
- Address of a stack variable can be obtained legally
There are broader changes too which make Go semantically different to other languages. Some of these features include support for :
- Concurrency
- Garbage Collection
- Interface Types
- Reflection
- Type Switches
Concurrency is one of the most important parameters in today’s computing environment. There are several servers running serving multiple clients in a distributed , dynamic environment. C++ and Java do not provide enough support for concurrency at the language level. Go uses a variant of CSP which is a perfect choice to add to a procedural programming model. It can be used orthogonally to provide extra power to the language without altering or limiting its daily usage. The idea is to execute each function independently as opposed to regular procedural programming. This results in more coupled concurrency with greater computational capability. It is easy to construct the software using CSP to handle clients as standalone procedures to have the full power of a compiled language for the costly encryption calculations. This model is a great fit for writing web servers. However, it has a limitation. It is not purely memory safe. This is because sharing of pointers is legal over a channel. Go’s concurrency model works well in a context which most programmers are used to. It encourages simple and safe code but in no way prevents bad code.
Garbage collection in Go is again an interesting concept. It has no explicit ‘free’ operation to deallocate memory. The only way memory is deallocated is through the garbage collector. The rationale behind this is that a lot of effort is spent by programmers in C and C++ to consciously manage memory. Garbage collection makes it easier to specify interfaces and makes memory an easier entity to manage. Furthermore, in a thread safe , concurrent programming environment , it is essential to have background memory management because it is very hard to manage a particular memory block which is being accessed by multiple threads at the same time. Code becomes easier to write because of garbage collection. Of course, there is no free lunch. There are naturally a few tradeoffs. There is an overhead of latency and complexity of implementation. However, the designers of Go believe that the benefits of garbage collection outcompete the limitations. Java also has garbage collection but it does not work well in a distributed server environment. There are often uncontrollable overheads , large latencies and a lot of tuning is required to get good performance. Go , as a language , somewhat dampens these ill effects. This is because of the general semantics of the language. Go gives the coder options to control the layout of data structures and to limit memory allocation. A simple example is this structure with an array of bytes :
type X struct {
a, b, c int buf [256]byte
}
In java , the byte array would need a second level of indirection. However in Go , the buffer is allocated in a single block of memory and there is no indirection. This is a better option for systems programming by reducing the number of items for the collector. This causes a significant difference when the system is large scale. Another example of efficient memory management in Go is when providing second order allocators. When an allocator is used to link a large array of structs in a list , it generates no garbage and is efficient.Although the garbage collector takes care of memory , good code can help place less stress on the collector. To give programmers this support, there are interior pointers to objects in the heap.It is legal to get the address of variable in the heap and pass it to a routine for I/O. This makes the garbage collection algorithms more complex however, it is important to give programmers the option to reduce the load on the garbage collector. In summary, Go is garbage collected but allows programmers some options to reduce overhead. The current design of the collector is a parallel mark-and-sweep design and is an active area of research. Go has a unique approach to object-oriented programming too. There is no type hierarchy or any type-based inheritance structure like subclassing.Instead, Go has interfaces, which are basically sets of methods,An example of an interface is: type Hash interface {
Write(p []byte) (n int, err error) Sum(b []byte) []byte Reset() Size() int BlockSize() int
}
Object oriented programming works on the basis that the data can be generalized without any dependence on the representation of the date. The model works most efficiently when the behaviour is fixed. Once a class is inherited and more methods are added, the behaviour changes. Go’s static interfaces make sure that the class behaviour is uniform which enables data to be uniform , orthogonal and safe. The makes of Go believe that type hierarchies result in brittle code. It involves modelling the system early and makes it very difficult to change the system once its written. This results in the designer making a lot of design decisions early on in the software development cycle. For adaptable systems, that grow as they are developed, Go is a great match. The resultant feature is that Go encourages composition , using simple interfaces to define behaviours which provide uniformity. It is a novel way of programming which takes some getting used to but provides great benefits with respect to adaptability. This structure allows the program to grow without any predetermined constraints.
Go does not have a well defined exception handling mechanism like Java. There is a predefined interface called error which returns a string. The reason for not having a separate error handling framework is that it interferes with the normal control flow of the code.