diff --git a/doc/go_tutorial.html b/doc/go_tutorial.html new file mode 100644 index 00000000000..b801f8e3690 --- /dev/null +++ b/doc/go_tutorial.html @@ -0,0 +1,1358 @@ +
+Rob Pike +
+
+
+This document is a tutorial introduction to the basics of the Go systems programming +language, intended for programmers familiar with C or C++. It is not a comprehensive +guide to the language; at the moment the document closest to that is the draft +specification: +
+
+ /doc/go_spec.html + ++To check out the compiler and tools and be ready to run Go programs, see +
+
+ /doc/go_setup.html + ++The presentation proceeds through a series of modest programs to illustrate +key features of the language. All the programs work (at time of writing) and are +checked in at +
+
+ /doc/progs + ++Program snippets are annotated with the line number in the original file; for +cleanliness, blank lines remain blank. +
+
+Let's start in the usual way: +
+
+01 package main +++03 import fmt "fmt" // Package implementing formatted I/O. +
+05 func main() { +06 fmt.Printf("Hello, world; or Καλημέρα κόσμε; or こんにちは 世界\n"); +07 } +
+Every Go source file declares, using a package
statement, which package it's part of.
+The main
package's main
function is where the program starts running (after
+any initialization). It may also import other packages to use their facilities.
+This program imports the package fmt
to gain access to
+our old, now capitalized and package-qualified friend, fmt.Printf
.
+
+Function declarations are introduced with the func
keyword.
+
+Notice that string constants can contain Unicode characters, encoded in UTF-8. +Go is defined to accept UTF-8 input. Strings are arrays of bytes, usually used +to store Unicode strings represented in UTF-8. +
+The comment convention is the same as in C++: +
+
+ /* ... */ + // ... + ++Later we'll have much more to say about printing. +
+
+Next up, here's a version of the Unix utility echo(1)
:
+
+
+01 package main +++03 import ( +04 "os"; +05 "flag"; +06 ) +
+08 var n_flag = flag.Bool("n", false, "don't print final newline") +
+10 const ( +11 kSpace = " "; +12 kNewline = "\n"; +13 ) +
+15 func main() { +16 flag.Parse(); // Scans the arg list and sets up flags +17 var s string = ""; +18 for i := 0; i < flag.NArg(); i++ { +19 if i > 0 { +20 s += kSpace +21 } +22 s += flag.Arg(i) +23 } +24 if !*n_flag { +25 s += kNewline +26 } +27 os.Stdout.WriteString(s); +28 } +
+This program is small but it's doing a number of new things. In the last example,
+we saw func
introducing a function. The keywords var
, const
, and type
+(not used yet) also introduce declarations, as does import
.
+Notice that we can group declarations of the same sort into
+parenthesized, semicolon-separated lists if we want, as on lines 3-6 and 10-13.
+But it's not necessary to do so; we could have said
+
+
+ const Space = " " + const Newline = "\n" + ++Semicolons aren't needed here; in fact, semicolons are unnecessary after any +top-level declaration, even though they are needed as separators within +a parenthesized list of declarations. +
+This program imports the "os"
package to access its Stdout
variable, of type
+*os.File
. The import
statement is actually a declaration: in its general form,
+as used in our ``hello world'' program,
+it names the identifier (fmt
)
+that will be used to access members of the package imported from the file ("fmt"
),
+found in the current directory or in a standard location.
+In this program, though, we've dropped the explicit name from the imports; by default,
+packages are imported using the name defined by the imported package,
+which by convention is of course the file name itself. Our ``hello world'' program
+could have said just import "fmt"
.
+
+You can specify your +own import names if you want but it's only necessary if you need to resolve +a naming conflict. +
+Given os.Stdout
we can use its WriteString
method to print the string.
+
+Having imported the flag
package, line 8 creates a global variable to hold
+the value of echo's -n
flag. The variable n_flag
has type *bool
, pointer
+to bool
.
+
+In main.main
, we parse the arguments (line 16) and then create a local
+string variable we will use to build the output.
+
+The declaration statement has the form +
+
+ var s string = ""; + ++This is the
var
keyword, followed by the name of the variable, followed by
+its type, followed by an equals sign and an initial value for the variable.
++Go tries to be terse, and this declaration could be shortened. Since the +string constant is of type string, we don't have to tell the compiler that. +We could write +
+
+ var s = ""; + ++or we could go even shorter and write the idiom +
+
+ s := ""; + ++The
:=
operator is used a lot in Go to represent an initializing declaration.
+(For those who know Limbo, its :=
construct is the same, but notice
+that Go has no colon after the name in a full var
declaration.
+Also, for simplicity of parsing, :=
only works inside functions, not at
+the top level.)
+There's one in the for
clause on the next line:
++
+18 for i := 0; i < flag.NArg(); i++ { ++
+The flag
package has parsed the arguments and left the non-flag arguments
+in a list that can be iterated over in the obvious way.
+
+The Go for
statement differs from that of C in a number of ways. First,
+it's the only looping construct; there is no while
or do
. Second,
+there are no parentheses on the clause, but the braces on the body
+are mandatory. The same applies to the if
and switch
statements.
+Later examples will show some other ways for
can be written.
+
+The body of the loop builds up the string s
by appending (using +=
)
+the flags and separating spaces. After the loop, if the -n
flag is not
+set, it appends a newline, and then writes the result.
+
+Notice that main.main
is a niladic function with no return type.
+It's defined that way. Falling off the end of main.main
means
+''success''; if you want to signal an erroneous return, call
+
+
+ os.Exit(1) + ++The
os
package contains other essentials for getting
+started; for instance, os.Args
is an array used by the
+flag
package to access the command-line arguments.
++
+Go has some familiar types such as int
and float
, which represent
+values of the ''appropriate'' size for the machine. It also defines
+specifically-sized types such as int8
, float64
, and so on, plus
+unsigned integer types such as uint
, uint32
, etc. These are
+distinct types; even if int
and int32
are both 32 bits in size,
+they are not the same type. There is also a byte
synonym for
+uint8
, which is the element type for strings.
+
+Speaking of string
, that's a built-in type as well. Strings are
+immutable values -- they are not just arrays of byte
values.
+Once you've built a string value, you can't change it, although
+of course you can change a string variable simply by
+reassigning it. This snippet from strings.go
is legal code:
+
+
+07 s := "hello"; +08 if s[1] != 'e' { os.Exit(1) } +09 s = "good bye"; +10 var p *string = &s; +11 *p = "ciao"; ++
+However the following statements are illegal because they would modify
+a string
value:
+
+
+ s[0] = 'x'; + (*p)[1] = 'y'; + ++In C++ terms, Go strings are a bit like
const strings
, while pointers
+to strings are analogous to const string
references.
++Yes, there are pointers. However, Go simplifies their use a little; +read on. +
+Arrays are declared like this: +
+
+ var array_of_int [10]int; + ++Arrays, like strings, are values, but they are mutable. This differs +from C, in which
array_of_int
would be usable as a pointer to int
.
+In Go, since arrays are values, it's meaningful (and useful) to talk
+about pointers to arrays.
+
+The size of the array is part of its type; however, one can declare
+a slice variable, to which one can assign a pointer to
+any array
+with the same element type or - much more commonly - a slice
+expression of the form a[low : high]
, representing
+the subarray indexed by low
through high-1
.
+Slices look a lot like arrays but have
+no explicit size ([]
vs. [10]
) and they reference a segment of
+an underlying, often anonymous, regular array. Multiple slices
+can share data if they represent pieces of the same array;
+multiple arrays can never share data.
+
+Slices are actually much more common in Go programs than +regular arrays; they're more flexible, have reference semantics, +and are efficient. What they lack is the precise control of storage +layout of a regular array; if you want to have a hundred elements +of an array stored within your structure, you should use a regular +array. +
+When passing an array to a function, you almost always want +to declare the formal parameter to be a slice. When you call +the function, take the address of the array and Go will automatically +create (efficiently) a slice reference and pass that. +
+Using slices one can write this function (from sum.go
):
+
+
+05 func sum(a []int) int { // returns an int +06 s := 0; +07 for i := 0; i < len(a); i++ { +08 s += a[i] +09 } +10 return s +11 } ++
+and invoke it like this: +
+
+15 s := sum(&[3]int{1,2,3}); // a slice of the array is passed to sum ++
+Note how the return type (int
) is defined for sum()
by stating it
+after the parameter list.
+The expression [3]int{1,2,3}
-- a type followed by a brace-bounded expression
+-- is a constructor for a value, in this case an array of 3 ints
. Putting an &
+in front gives us the address of a unique instance of the value. We pass the
+pointer to sum()
by (automatically) promoting it to a slice.
+
+If you are creating a regular array but want the compiler to count the
+elements for you, use ...
as the array size:
+
+
+ s := sum(&[...]int{1,2,3}); + ++In practice, though, unless you're meticulous about storage layout within a +data structure, a slice itself - using empty brackets and no
&
- is all you need:
++
+ s := sum([]int{1,2,3}); + ++There are also maps, which you can initialize like this: +
+
+ m := map[string] int {"one":1 , "two":2} + ++The built-in function
len()
, which returns number of elements,
+makes its first appearance in sum
. It works on strings, arrays,
+slices, and maps.
++
+
+Most types in Go are values. If you have an int
or a struct
+or an array, assignment
+copies the contents of the object. To allocate something on the stack,
+just declare a variable. To allocate it on the heap, use new()
, which
+returns a pointer to the allocated storage.
+
+
+ type T struct { a, b int } + var t *T = new(T); + ++or the more idiomatic +
+
+ t := new(T); + ++Some types - maps, slices, and channels (see below) - have reference semantics. +If you're holding a slice or a map and you modify its contents, other variables +referencing the same underlying data will see the modification. For these three +types you want to use the built-in function
make()
:
++
+ m := make(map[string] int); + ++This statement initializes a new map ready to store entries. +If you just declare the map, as in +
+
+ var m map[string] int; + ++it creates a
nil
reference that cannot hold anything. To use the map,
+you must first initialize the reference using make()
or by assignment to an
+existing map.
+
+Note that new(T)
returns type *T
while make(T)
returns type
+T
. If you (mistakenly) allocate a reference object with new()
,
+you receive a pointer to an uninitialized reference, equivalent to
+declaring an uninitialized variable and taking its address.
+
+
+Although integers come in lots of sizes in Go, integer constants do not.
+There are no constants like 0ll
or 0x0UL
. Instead, integer
+constants are evaluated as ideal, large-precision values that
+can overflow only when they are assigned to an integer variable with
+too little precision to represent the value.
+
+
+ const hard_eight = (1 << 100) >> 97 // legal + ++There are nuances that deserve redirection to the legalese of the +language specification but here are some illustrative examples: +
+
+ var a uint64 = 0 // a has type uint64, value 0 + a := uint64(0) // equivalent; use a "conversion" + i := 0x1234 // i gets default type: int + var j int = 1e6 // legal - 1000000 is representable in an int + x := 1.5 // a float + i3div2 := 3/2 // integer division - result is 1 + f3div2 := 3./2. // floating point division - result is 1.5 + ++Conversions only work for simple cases such as converting
ints
of one
+sign or size to another, and between ints
and floats
, plus a few other
+simple cases. There are no automatic numeric conversions of any kind in Go,
+other than that of making constants have concrete size and type when
+assigned to a variable.
++
+Next we'll look at a simple package for doing file I/O with the usual
+sort of open/close/read/write interface. Here's the start of file.go
:
+
+
+01 package file +++03 import ( +04 "os"; +05 "syscall"; +06 ) +
+08 type File struct { +09 fd int; // file descriptor number +10 name string; // file name at Open time +11 } +
+The first line declares the name of the package -- file
--
+and then we import two packages. The os
package hides the differences
+between various operating systems to give a consistent view of files and
+so on; here we're only going to use its error handling utilities
+and reproduce the rudiments of its file I/O.
+
+The other item is the low-level, external syscall
package, which provides
+a primitive interface to the underlying operating system's calls.
+
+Next is a type definition: the type
keyword introduces a type declaration,
+in this case a data structure called File
.
+To make things a little more interesting, our File
includes the name of the file
+that the file descriptor refers to.
+
+Because File
starts with a capital letter, the type is available outside the package,
+that is, by users of the package. In Go the rule about visibility of information is
+simple: if a name (of a top-level type, function, method, constant, variable, or of
+a structure field) is capitalized, users of the package may see it. Otherwise, the
+name and hence the thing being named is visible only inside the package in which
+it is declared. This is more than a convention; the rule is enforced by the compiler.
+In Go, the term for publicly visible names is ''exported''.
+
+In the case of File
, all its fields are lower case and so invisible to users, but we
+will soon give it some exported, upper-case methods.
+
+First, though, here is a factory to create them: +
+
+13 func newFile(fd int, name string) *File { +14 if fd < 0 { +15 return nil +16 } +17 return &File{fd, name} +18 } ++
+This returns a pointer to a new File
structure with the file descriptor and name
+filled in. This code uses Go's notion of a ''composite literal'', analogous to
+the ones used to build maps and arrays, to construct a new heap-allocated
+object. We could write
+
+
+ n := new(File); + n.fd = fd; + n.name = name; + return n + ++but for simple structures like
File
it's easier to return the address of a nonce
+composite literal, as is done here on line 17.
+
+We can use the factory to construct some familiar, exported variables of type *File
:
+
+
+20 var ( +21 Stdin = newFile(0, "/dev/stdin"); +22 Stdout = newFile(1, "/dev/stdout"); +23 Stderr = newFile(2, "/dev/stderr"); +24 ) ++
+The newFile
function was not exported because it's internal. The proper,
+exported factory to use is Open
:
+
+
+26 func Open(name string, mode int, perm int) (file *File, err os.Error) { +27 r, e := syscall.Open(name, mode, perm); +28 if e != 0 { +29 err = os.Errno(e); +30 } +31 return newFile(r, name), err +32 } ++
+There are a number of new things in these few lines. First, Open
returns
+multiple values, an File
and an error (more about errors in a moment).
+We declare the
+multi-value return as a parenthesized list of declarations; syntactically
+they look just like a second parameter list. The function
+syscall.Open
+also has a multi-value return, which we can grab with the multi-variable
+declaration on line 27; it declares r
and e
to hold the two values,
+both of type int64
(although you'd have to look at the syscall
package
+to see that). Finally, line 28 returns two values: a pointer to the new File
+and the error. If syscall.Open
fails, the file descriptor r
will
+be negative and NewFile
will return nil
.
+
+About those errors: The os
library includes a general notion of an error
+string, maintaining a unique set of errors throughout the program. It's a
+good idea to use its facility in your own interfaces, as we do here, for
+consistent error handling throughout Go code. In Open
we use a
+conversion to os.Errno
to translate Unix's integer errno
value into
+an error value, which will be stored in a unique instance of type os.Error
.
+
+Now that we can build Files
, we can write methods for them. To declare
+a method of a type, we define a function to have an explicit receiver
+of that type, placed
+in parentheses before the function name. Here are some methods for *File
,
+each of which declares a receiver variable file
.
+
+
+34 func (file *File) Close() os.Error { +35 if file == nil { +36 return os.EINVAL +37 } +38 e := syscall.Close(file.fd); +39 file.fd = -1; // so it can't be closed again +40 if e != 0 { +41 return os.Errno(e); +42 } +43 return nil +44 } +++46 func (file *File) Read(b []byte) (ret int, err os.Error) { +47 if file == nil { +48 return -1, os.EINVAL +49 } +50 r, e := syscall.Read(file.fd, b); +51 if e != 0 { +52 err = os.Errno(e); +53 } +54 return int(r), err +55 } +
+57 func (file *File) Write(b []byte) (ret int, err os.Error) { +58 if file == nil { +59 return -1, os.EINVAL +60 } +61 r, e := syscall.Write(file.fd, b); +62 if e != 0 { +63 err = os.Errno(e); +64 } +65 return int(r), err +66 } +
+68 func (file *File) String() string { +69 return file.name +70 } +
+There is no implicit this
and the receiver variable must be used to access
+members of the structure. Methods are not declared within
+the struct
declaration itself. The struct
declaration defines only data members.
+In fact, methods can be created for any type you name, such as an integer or
+array, not just for structs
. We'll see an example with arrays later.
+
+The String
method is so called because of printing convention we'll
+describe later.
+
+The methods use the public variable os.EINVAL
to return the (os.Error
+version of the) Unix error code EINVAL
. The os
library defines a standard
+set of such error values.
+
+We can now use our new package: +
+
+01 package main +++03 import ( +04 "./file"; +05 "fmt"; +06 "os"; +07 ) +
+09 func main() { +10 hello := []byte{'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n'}; +11 file.Stdout.Write(hello); +12 file, err := file.Open("/does/not/exist", 0, 0); +13 if file == nil { +14 fmt.Printf("can't open file; err=%s\n", err.String()); +15 os.Exit(1); +16 } +17 } +
+The import of ''./file
'' tells the compiler to use our own package rather than
+something from the directory of installed packages.
+
+Finally we can run the program: +
+
+ % helloworld3 + hello, world + can't open file; err=No such file or directory + % + ++
+Building on the file
package, here's a simple version of the Unix utility cat(1)
,
+progs/cat.go
:
+
+
+01 package main +++03 import ( +04 "./file"; +05 "flag"; +06 "fmt"; +07 "os"; +08 ) +
+10 func cat(f *file.File) { +11 const NBUF = 512; +12 var buf [NBUF]byte; +13 for { +14 switch nr, er := f.Read(&buf); true { +15 case nr < 0: +16 fmt.Fprintf(os.Stderr, "error reading from %s: %s\n", f.String(), er.String()); +17 os.Exit(1); +18 case nr == 0: // EOF +19 return; +20 case nr > 0: +21 if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr { +22 fmt.Fprintf(os.Stderr, "error writing from %s: %s\n", f.String(), ew.String()); +23 } +24 } +25 } +26 } +
+28 func main() { +29 flag.Parse(); // Scans the arg list and sets up flags +30 if flag.NArg() == 0 { +31 cat(file.Stdin); +32 } +33 for i := 0; i < flag.NArg(); i++ { +34 f, err := file.Open(flag.Arg(i), 0, 0); +35 if f == nil { +36 fmt.Fprintf(os.Stderr, "can't open %s: error %s\n", flag.Arg(i), err); +37 os.Exit(1); +38 } +39 cat(f); +40 f.Close(); +41 } +42 } +
+By now this should be easy to follow, but the switch
statement introduces some
+new features. Like a for
loop, an if
or switch
can include an
+initialization statement. The switch
on line 14 uses one to create variables
+nr
and er
to hold the return values from f.Read()
. (The if
on line 21
+has the same idea.) The switch
statement is general: it evaluates the cases
+from top to bottom looking for the first case that matches the value; the
+case expressions don't need to be constants or even integers, as long as
+they all have the same type.
+
+Since the switch
value is just true
, we could leave it off -- as is also
+the situation
+in a for
statement, a missing value means true
. In fact, such a switch
+is a form of if-else
chain. While we're here, it should be mentioned that in
+switch
statements each case
has an implicit break
.
+
+Line 21 calls Write()
by slicing the incoming buffer, which is itself a slice.
+Slices provide the standard Go way to handle I/O buffers.
+
+Now let's make a variant of cat
that optionally does rot13
on its input.
+It's easy to do by just processing the bytes, but instead we will exploit
+Go's notion of an interface.
+
+The cat()
subroutine uses only two methods of f
: Read()
and String()
,
+so let's start by defining an interface that has exactly those two methods.
+Here is code from progs/cat_rot13.go
:
+
+
+22 type reader interface { +23 Read(b []byte) (ret int, err os.Error); +24 String() string; +25 } ++
+Any type that implements the two methods of reader
-- regardless of whatever
+other methods the type may also contain -- is said to implement the
+interface. Since file.File
implements these methods, it implements the
+reader
interface. We could tweak the cat
subroutine to accept a reader
+instead of a *file.File
and it would work just fine, but let's embellish a little
+first by writing a second type that implements reader
, one that wraps an
+existing reader
and does rot13
on the data. To do this, we just define
+the type and implement the methods and with no other bookkeeping,
+we have a second implementation of the reader
interface.
+
+
+27 type rotate13 struct { +28 source reader; +29 } +++31 func newRotate13(source reader) *rotate13 { +32 return &rotate13{source} +33 } +
+35 func (r13 *rotate13) Read(b []byte) (ret int, err os.Error) { +36 r, e := r13.source.Read(b); +37 for i := 0; i < r; i++ { +38 b[i] = rot13(b[i]) +39 } +40 return r, e +41 } +
+43 func (r13 *rotate13) String() string { +44 return r13.source.String() +45 } +46 // end of rotate13 implementation +
+(The rot13
function called on line 38 is trivial and not worth reproducing.)
+
+To use the new feature, we define a flag: +
+
+10 var rot13_flag = flag.Bool("rot13", false, "rot13 the input") ++
+and use it from within a mostly unchanged cat()
function:
+
+
+48 func cat(r reader) { +49 const NBUF = 512; +50 var buf [NBUF]byte; +++52 if *rot13_flag { +53 r = newRotate13(r) +54 } +55 for { +56 switch nr, er := r.Read(&buf); { +57 case nr < 0: +58 fmt.Fprintf(os.Stderr, "error reading from %s: %s\n", r.String(), er.String()); +59 os.Exit(1); +60 case nr == 0: // EOF +61 return; +62 case nr > 0: +63 nw, ew := file.Stdout.Write(buf[0:nr]); +64 if nw != nr { +65 fmt.Fprintf(os.Stderr, "error writing from %s: %s\n", r.String(), ew.String()); +66 } +67 } +68 } +69 } +
+(We could also do the wrapping in main
and leave cat()
mostly alone, except
+for changing the type of the argument; consider that an exercise.)
+Lines 52 through 55 set it all up: If the rot13
flag is true, wrap the reader
+we received into a rotate13
and proceed. Note that the interface variables
+are values, not pointers: the argument is of type reader
, not *reader
,
+even though under the covers it holds a pointer to a struct
.
+
+Here it is in action: +
+
+ % echo abcdefghijklmnopqrstuvwxyz | ./cat + abcdefghijklmnopqrstuvwxyz + % echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13 + nopqrstuvwxyzabcdefghijklm + % ++
+Fans of dependency injection may take cheer from how easily interfaces +allow us to substitute the implementation of a file descriptor. +
+Interfaces are a distinct feature of Go. An interface is implemented by a
+type if the type implements all the methods declared in the interface.
+This means
+that a type may implement an arbitrary number of different interfaces.
+There is no type hierarchy; things can be much more ad hoc,
+as we saw with rot13
. The type file.File
implements reader
; it could also
+implement a writer
, or any other interface built from its methods that
+fits the current situation. Consider the empty interface
+
+
+ type Empty interface {} ++
+Every type implements the empty interface, which makes it +useful for things like containers. +
+
+Interfaces provide a simple form of polymorphism since they completely +separate the definition of what an object does from how it does it, allowing +distinct implementations to be represented at different times by the +same interface variable. +
+As an example, consider this simple sort algorithm taken from progs/sort.go
:
+
+
+09 func Sort(data SortInterface) { +10 for i := 1; i < data.Len(); i++ { +11 for j := i; j > 0 && data.Less(j, j-1); j-- { +12 data.Swap(j, j-1); +13 } +14 } +15 } ++
+The code needs only three methods, which we wrap into SortInterface
:
+
+
+03 type SortInterface interface { +04 Len() int; +05 Less(i, j int) bool; +06 Swap(i, j int); +07 } ++
+We can apply Sort
to any type that implements Len
, Less
, and Swap
.
+The sort
package includes the necessary methods to allow sorting of
+arrays of integers, strings, etc.; here's the code for arrays of int
+
+
+29 type IntArray []int +++31 func (p IntArray) Len() int { return len(p); } +32 func (p IntArray) Less(i, j int) bool { return p[i] < p[j]; } +33 func (p IntArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } +
+
+36 type FloatArray []float +
+38 func (p FloatArray) Len() int { return len(p); } +39 func (p FloatArray) Less(i, j int) bool { return p[i] < p[j]; } +40 func (p FloatArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } +
+
+43 type StringArray []string +
+45 func (p StringArray) Len() int { return len(p); } +46 func (p StringArray) Less(i, j int) bool { return p[i] < p[j]; } +47 func (p StringArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } +
+
+50 // Convenience wrappers for common cases +
+52 func SortInts(a []int) { Sort(IntArray(a)); } +53 func SortFloats(a []float) { Sort(FloatArray(a)); } +54 func SortStrings(a []string) { Sort(StringArray(a)); } +
+
+57 func IntsAreSorted(a []int) bool { return IsSorted(IntArray(a)); } +58 func FloatsAreSorted(a []float) bool { return IsSorted(FloatArray(a)); } +59 func StringsAreSorted(a []string) bool { return IsSorted(StringArray(a)); } +
+Here we see methods defined for non-struct
types. You can define methods
+for any type you define and name in your package.
+
+And now a routine to test it out, from progs/sortmain.go
. This
+uses a function in the sort
package, omitted here for brevity,
+to test that the result is sorted.
+
+
+08 func ints() { +09 data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}; +10 a := sort.IntArray(data); +11 sort.Sort(a); +12 if !sort.IsSorted(a) { +13 panic() +14 } +15 } ++
+If we have a new type we want to be able to sort, all we need to do is +to implement the three methods for that type, like this: +
+
+26 type day struct { +27 num int; +28 short_name string; +29 long_name string; +30 } +++32 type dayArray struct { +33 data []*day; +34 } +
+36 func (p *dayArray) Len() int { return len(p.data); } +37 func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num; } +38 func (p *dayArray) Swap(i, j int) { p.data[i], p.data[j] = p.data[j], p.data[i]; } +
+
+
+The examples of formatted printing so far have been modest. In this section +we'll talk about how formatted I/O can be done well in Go. +
+We've seen simple uses of the package fmt
, which
+implements Printf
, Fprintf
, and so on.
+Within the fmt
package, Printf
is declared with this signature:
+
+
+ Printf(format string, v ...) (n int, errno os.Error) + ++That
...
represents the variadic argument list that in C would
+be handled using the stdarg.h
macros, but in Go is passed using
+an empty interface variable (interface {}
) that is then unpacked
+using the reflection library. It's off topic here but the use of
+reflection helps explain some of the nice properties of Go's Printf
,
+due to the ability of Printf
to discover the type of its arguments
+dynamically.
+
+For example, in C each format must correspond to the type of its
+argument. It's easier in many cases in Go. Instead of %llud
you
+can just say %d
; Printf
knows the size and signedness of the
+integer and can do the right thing for you. The snippet
+
+
+06 var u64 uint64 = 1<<64-1; +07 fmt.Printf("%d %d\n", u64, int64(u64)); ++
+prints +
+
+ 18446744073709551615 -1 + ++In fact, if you're lazy the format
%v
will print, in a simple
+appropriate style, any value, even an array or structure. The output of
++
+10 type T struct { a int; b string }; +11 t := T{77, "Sunset Strip"}; +12 a := []int{1, 2, 3, 4}; +13 fmt.Printf("%v %v %v\n", u64, t, a); ++
+is +
+
+ 18446744073709551615 {77 Sunset Strip} [1 2 3 4] + ++You can drop the formatting altogether if you use
Print
or Println
+instead of Printf
. Those routines do fully automatic formatting.
+The Print
function just prints its elements out using the equivalent
+of %v
while Println
automatically inserts spaces between arguments
+and adds a newline. The output of each of these two lines is identical
+to that of the Printf
call above.
++
+14 fmt.Print(u64, " ", t, " ", a, "\n"); +15 fmt.Println(u64, t, a); ++
+If you have your own type you'd like Printf
or Print
to format,
+just give it a String()
method that returns a string. The print
+routines will examine the value to inquire whether it implements
+the method and if so, use it rather than some other formatting.
+Here's a simple example.
+
+
+05 type testType struct { a int; b string } +++07 func (t *testType) String() string { +08 return fmt.Sprint(t.a) + " " + t.b +09 } +
+11 func main() { +12 t := &testType{77, "Sunset Strip"}; +13 fmt.Println(t) +14 } +
+Since *T
has a String()
method, the
+default formatter for that type will use it and produce the output
+
+
+ 77 Sunset Strip + ++Observe that the
String()
method calls Sprint
(the obvious Go
+variant that returns a string) to do its formatting; special formatters
+can use the fmt
library recursively.
+
+Another feature of Printf
is that the format %T
will print a string
+representation of the type of a value, which can be handy when debugging
+polymorphic code.
+
+It's possible to write full custom print formats with flags and precisions +and such, but that's getting a little off the main thread so we'll leave it +as an exploration exercise. +
+You might ask, though, how Printf
can tell whether a type implements
+the String()
method. Actually what it does is ask if the value can
+be converted to an interface variable that implements the method.
+Schematically, given a value v
, it does this:
+
+
+
+ type Stringer interface { + String() string + } + + s, ok := v.(Stringer); // Test whether v implements "String()" + if ok { + result = s.String() + } else { + result = default_output(v) + } + ++The code uses a ``type assertion'' (
v.(Stringer)
) to test if the value stored in
+v
satisfies the Stringer
interface; if it does, s
+will become an interface variable implementing the method and ok
will
+be true
. We then use the interface variable to call the method.
+(The ''comma, ok'' pattern is a Go idiom used to test the success of
+operations such as type conversion, map update, communications, and so on,
+although this is the only appearance in this tutorial.)
+If the value does not satisfy the interface, ok
will be false.
+
+In this snippet the name Stringer
follows the convention that we add [e]r
+to interfaces describing simple method sets like this.
+
+One last wrinkle. To complete the suite, besides Printf
etc. and Sprintf
+etc., there are also Fprintf
etc. Unlike in C, Fprintf
's first argument is
+not a file. Instead, it is a variable of type io.Writer
, which is an
+interface type defined in the io
library:
+
+
+ type Writer interface { + Write(p []byte) (n int, err os.Error); + } + ++(This interface is another conventional name, this time for
Write
; there are also
+io.Reader
, io.ReadWriter
, and so on.)
+Thus you can call Fprintf
on any type that implements a standard Write()
+method, not just files but also network channels, buffers, rot13ers, whatever
+you want.
++
+Now we come to processes and communication -- concurrent programming. +It's a big subject so to be brief we assume some familiarity with the topic. +
+A classic program in the style is the prime sieve of Eratosthenes. +It works by taking a stream of all the natural numbers and introducing +a sequence of filters, one for each prime, to winnow the multiples of +that prime. At each step we have a sequence of filters of the primes +so far, and the next number to pop out is the next prime, which triggers +the creation of the next filter in the chain. +
+Here's a flow diagram; each box represents a filter element whose +creation is triggered by the first number that flowed from the +elements before it. +
+
+
+ +
+
+
+To create a stream of integers, we use a Go channel, which,
+borrowing from CSP's descendants, represents a communications
+channel that can connect two concurrent computations.
+In Go, channel variables are references to a run-time object that
+coordinates the communication; as with maps and slices, use
+make
to create a new channel.
+
+Here is the first function in progs/sieve.go
:
+
+
+05 // Send the sequence 2, 3, 4, ... to channel 'ch'. +06 func generate(ch chan int) { +07 for i := 2; ; i++ { +08 ch <- i // Send 'i' to channel 'ch'. +09 } +10 } ++
+The generate
function sends the sequence 2, 3, 4, 5, ... to its
+argument channel, ch
, using the binary communications operator <-
.
+Channel operations block, so if there's no recipient for the value on ch
,
+the send operation will wait until one becomes available.
+
+The filter
function has three arguments: an input channel, an output
+channel, and a prime number. It copies values from the input to the
+output, discarding anything divisible by the prime. The unary communications
+operator <-
(receive) retrieves the next value on the channel.
+
+
+12 // Copy the values from channel 'in' to channel 'out', +13 // removing those divisible by 'prime'. +14 func filter(in, out chan int, prime int) { +15 for { +16 i := <-in; // Receive value of new variable 'i' from 'in'. +17 if i % prime != 0 { +18 out <- i // Send 'i' to channel 'out'. +19 } +20 } +21 } ++
+The generator and filters execute concurrently. Go has
+its own model of process/threads/light-weight processes/coroutines,
+so to avoid notational confusion we'll call concurrently executing
+computations in Go goroutines. To start a goroutine,
+invoke the function, prefixing the call with the keyword go
;
+this starts the function running in parallel with the current
+computation but in the same address space:
+
+
+ go sum(huge_array); // calculate sum in the background + ++If you want to know when the calculation is done, pass a channel +on which it can report back: +
+
+ ch := make(chan int); + go sum(huge_array, ch); + // ... do something else for a while + result := <-ch; // wait for, and retrieve, result + ++Back to our prime sieve. Here's how the sieve pipeline is stitched +together: +
+
+24 func main() { +25 ch := make(chan int); // Create a new channel. +26 go generate(ch); // Start generate() as a goroutine. +27 for { +28 prime := <-ch; +29 fmt.Println(prime); +30 ch1 := make(chan int); +31 go filter(ch, ch1, prime); +32 ch = ch1 +33 } +34 } ++
+Line 25 creates the initial channel to pass to generate
, which it
+then starts up. As each prime pops out of the channel, a new filter
+is added to the pipeline and its output becomes the new value
+of ch
.
+
+The sieve program can be tweaked to use a pattern common
+in this style of programming. Here is a variant version
+of generate
, from progs/sieve1.go
:
+
+
+06 func generate() chan int { +07 ch := make(chan int); +08 go func(){ +09 for i := 2; ; i++ { +10 ch <- i +11 } +12 }(); +13 return ch; +14 } ++
+This version does all the setup internally. It creates the output +channel, launches a goroutine internally using a function literal, and +returns the channel to the caller. It is a factory for concurrent +execution, starting the goroutine and returning its connection. +
+The function literal notation (lines 8-12) allows us to construct an
+anonymous function and invoke it on the spot. Notice that the local
+variable ch
is available to the function literal and lives on even
+after generate
returns.
+
+The same change can be made to filter
:
+
+
+17 func filter(in chan int, prime int) chan int { +18 out := make(chan int); +19 go func() { +20 for { +21 if i := <-in; i % prime != 0 { +22 out <- i +23 } +24 } +25 }(); +26 return out; +27 } ++
+The sieve
function's main loop becomes simpler and clearer as a
+result, and while we're at it let's turn it into a factory too:
+
+
+29 func sieve() chan int { +30 out := make(chan int); +31 go func() { +32 ch := generate(); +33 for { +34 prime := <-ch; +35 out <- prime; +36 ch = filter(ch, prime); +37 } +38 }(); +39 return out; +40 } ++
+Now main
's interface to the prime sieve is a channel of primes:
+
+
+42 func main() { +43 primes := sieve(); +44 for { +45 fmt.Println(<-primes); +46 } +47 } ++
+
+With channels, it's possible to serve multiple independent client goroutines without
+writing an actual multiplexer. The trick is to send the server a channel in the message,
+which it will then use to reply to the original sender.
+A realistic client-server program is a lot of code, so here is a very simple substitute
+to illustrate the idea. It starts by defining a request
type, which embeds a channel
+that will be used for the reply.
+
+
+05 type request struct { +06 a, b int; +07 replyc chan int; +08 } ++
+The server will be trivial: it will do simple binary operations on integers. Here's the +code that invokes the operation and responds to the request: +
+
+10 type binOp func(a, b int) int +++12 func run(op binOp, req *request) { +13 reply := op(req.a, req.b); +14 req.replyc <- reply; +15 } +
+Line 10 defines the name binOp
to be a function taking two integers and
+returning a third.
+
+The server
routine loops forever, receiving requests and, to avoid blocking due to
+a long-running operation, starting a goroutine to do the actual work.
+
+
+17 func server(op binOp, service chan *request) { +18 for { +19 req := <-service; +20 go run(op, req); // don't wait for it +21 } +22 } ++
+We construct a server in a familiar way, starting it up and returning a channel to +connect to it: +
+
+24 func startServer(op binOp) chan *request { +25 req := make(chan *request); +26 go server(op, req); +27 return req; +28 } ++
+Here's a simple test. It starts a server with an addition operator, and sends out +lots of requests but doesn't wait for the reply. Only after all the requests are sent +does it check the results. +
+
+30 func main() { +31 adder := startServer(func(a, b int) int { return a + b }); +32 const N = 100; +33 var reqs [N]request; +34 for i := 0; i < N; i++ { +35 req := &reqs[i]; +36 req.a = i; +37 req.b = i + N; +38 req.replyc = make(chan int); +39 adder <- req; +40 } +41 for i := N-1; i >= 0; i-- { // doesn't matter what order +42 if <-reqs[i].replyc != N + 2*i { +43 fmt.Println("fail at", i); +44 } +45 } +46 fmt.Println("done"); +47 } ++
+One annoyance with this program is that it doesn't exit cleanly; when main
returns
+there are a number of lingering goroutines blocked on communication. To solve this,
+we can provide a second, quit
channel to the server:
+
+
+28 func startServer(op binOp) (service chan *request, quit chan bool) { +29 service = make(chan *request); +30 quit = make(chan bool); +31 go server(op, service, quit); +32 return service, quit; +33 } ++
+It passes the quit channel to the server
function, which uses it like this:
+
+
+17 func server(op binOp, service chan *request, quit chan bool) { +18 for { +19 select { +20 case req := <-service: +21 go run(op, req); // don't wait for it +22 case <-quit: +23 return; +24 } +25 } +26 } ++
+Inside server
, a select
statement chooses which of the multiple communications
+listed by its cases can proceed. If all are blocked, it waits until one can proceed; if
+multiple can proceed, it chooses one at random. In this instance, the select
allows
+the server to honor requests until it receives a quit message, at which point it
+returns, terminating its execution.
+
+
+All that's left is to strobe the quit
channel
+at the end of main:
+
+
+36 adder, quit := startServer(func(a, b int) int { return a + b }); ++... +
+51 quit <- true; ++
+There's a lot more to Go programming and concurrent programming in general but this +quick tour should give you some of the basics. + +