From 3e7407915126942f902d86d94430efd7bd7a1cef Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Mon, 5 Oct 2009 14:48:57 -0700 Subject: [PATCH] slices methods on slices, pointers and values. R=iant,rsc,gri DELTA=173 (150 added, 1 deleted, 22 changed) OCL=35333 CL=35337 --- doc/effective_go.html | 195 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 172 insertions(+), 23 deletions(-) diff --git a/doc/effective_go.html b/doc/effective_go.html index d72525c999c..af1febe2c84 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -5,11 +5,12 @@

Introduction

-Go is a new language. Although it's in the C family -it has some unusual properties that make effective Go programs -different in character from programs in existing languages. +Go is a new language. Although it borrows ideas from +existing languages, +it has unusual properties that make effective Go programs +different in character from programs in its relatives. A straightforward translation of a C++ or Java program into Go -is unlikely to produce a satisfactory result—Java programs +is unlikely to produce a satisfactory result—Java programs are written in Java, not Go. On the other hand, thinking about the problem from a Go perspective could produce a successful but quite different @@ -257,8 +258,7 @@ var ( Names are as important in Go as in any other language. In some cases they even have semantic effect: for instance, the visibility of a name outside a package is determined by whether its -first character is an upper case letter, -while methods are looked up by name alone (although the type must match too). +first character is an upper case letter. It's therefore worth spending a little time talking about naming conventions in Go programs.

@@ -288,6 +288,8 @@ And don't worry about collisions a priori. The package name is only the default name for imports; it need not be unique across all source code, and in the rare case of a collision the importing package can choose a different name to use locally. +In any case, confusion is rare because the file name in the import +defines which version is being used.

@@ -310,7 +312,7 @@ Moreover, because imported entities are always addressed with their package name, bufio.Reader does not conflict with io.Reader. Similarly, the constructor for vector.Vector -could be called NewVector but since +would normally be called NewVector but since Vector is the only type exported by the package, and since the package is called vector, it's called just New, which clients of the package see as vector.New. @@ -352,7 +354,7 @@ call your string-converter method String not ToString.

MixedCaps

-Finally, the convention in Go is to used MixedCaps +Finally, the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names.

@@ -418,7 +420,8 @@ slightly generalized initialization statement like that of for; and there are new control structures including a type switch and a multiway communications multiplexer, select. -The syntax is also slightly different: parentheses are not part of the syntax +The syntax is also slightly different: +parentheses are not required and the bodies must always be brace-delimited.

@@ -524,7 +527,7 @@ it all up for you:
 var m map[string] int;
 sum := 0;
-for key, value := range m {  // key is unused; could call it '_'
+for _, value := range m {  // key is unused
     sum += value
 }
 
@@ -633,7 +636,7 @@ func Compare(a, b []byte) int {

One of Go's unusual properties is that functions and methods can return multiple values. This feature can be used to -improve on a couple of clumsy idioms in C program: in-band +improve on a couple of clumsy idioms in C programs: in-band error returns (-1 for EOF for example) and modifying an argument.

@@ -660,7 +663,8 @@ This is a common style; see the section on error handling for more examples.

A similar approach obviates the need to pass a pointer to a return -value to overwrite an argument. Here's a simple-minded function to +value to simulate a reference parameter. +Here's a simple-minded function to grab a number from a position in a byte array, returning the number and the next position.

@@ -693,7 +697,7 @@ You could use it to scan the numbers in an input array a like this:

The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters. -When named, they are initialized to the zero for their type when +When named, they are initialized to the zero values for their types when the function begins; if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values. @@ -825,8 +829,9 @@ func NewFile(fd int, name string) *File { Note that it's perfectly OK to return the address of a local variable; the storage associated with the variable survives after the function returns. -In fact, as a special case, the address of a composite literal -allocates a fresh instance each time, we can combine these last two lines: +In fact, taking the address of a composite literal +allocates a fresh instance each time it is evaluated, +so we can combine these last two lines:

@@ -972,10 +977,84 @@ But even this style isn't idiomatic Go.  Slices are.
 

Slices

-Slices wrap arrays to give a more general, powerful, and convenient interface to sequences -of data. -Except for items with explicit dimension such as rotation matrices, most -array programming in Go is done with slices rather than simple arrays. +Slices wrap arrays to give a more general, powerful, and convenient +interface to sequences of data. Except for items with explicit +dimension such as transformation matrices, most array programming in +Go is done with slices rather than simple arrays. +

+

+Slices are reference types, which means that if you assign one +slice to another, both refer to the same underlying array. For +instance, if a function takes a slice argument, changes it makes to +the elements of the slice will be visible to the caller, analogous to +passing a pointer to the underlying array. A Read +function can therefore accept a slice argument rather than a (pointer +to an) array and a count; the length within the slice sets an upper +limit of how much data to read. Here is the signature of the +Read method of the File type in package +os: +

+
+func (file *File) Read(buf []byte) (n int, err os.Error)
+
+

+The method returns the number of bytes read and an error value, if +any. To read into the first 32 bytes of a larger buffer +b, slice (here used as a verb) the buffer: +

+
+	n, err := f.Read(buf[0:32]);
+
+

+Such slicing is common and efficient. In fact, leaving efficiency aside for +the moment, this snippet would also read the first 32 bytes of the buffer: +

+
+	var n int;
+	var err os.Error;
+	for i := 0; i < 32; i++ {
+		nbytes, e := f.Read(buf[i:i+1]);
+		if nbytes == 0 || e != nil {
+			err = e;
+			break;
+		}
+		n += nbytes;
+	}
+
+

+The length of a slice may be changed as long as it still fits within +the limits of the underyling array; just assign it to a slice of +itself. The capacity of a slice, accessible by the built-in +function cap, reports the maximum length the slice may +assume. Here is a function to append data to a slice. If the data +exceeds the capacity, the slice is reallocated. The +resulting slice is returned. The function uses the fact that +len and cap are legal when applied to the +nil slice, and return 0. +

+
+func Append(slice, data[]byte) []byte {
+	l := len(slice);
+	if l + len(data) > cap(slice) {	// reallocate
+		// Allocate double what's needed, for future growth.
+		newSlice := make([]byte, (l+len(data))*2);
+		// Copy data (could use bytes.Copy()).
+		for i, c := range slice {
+			newSlice[i] = c
+		}
+		slice = newSlice;
+	}
+	slice = slice[0:l+len(data)];
+	for i, c := range data {
+		slice[l+i] = c
+	}
+	return slice;
+}
+
+

+We must return the slice afterwards because, although Append +can modify the elements of slice, the slice itself (the run-time data +structure holding the pointer, length, and capacity) is passed by value.

@@ -983,10 +1062,71 @@ array programming in Go is done with slices rather than simple arrays.

Printing

Methods

-

Basics

-

Pointers vs. Values

-

Methods on arbitrary types

+

Pointers vs. Values

+

+Methods can be defined for any named type except pointers and interfaces; +the receiver does not have to be a struct. +

+In the discussion of slices above, we wrote an Append +function. We can define it as a method on slices instead. To do +this, we first declare a named type to which we can bind the method, and +then make the receiver for the method a value of that type. +

+
+type ByteSlice []byte
+
+func (slice ByteSlice) Append(data []byte) []slice {
+	// Body exactly the same as above
+}
+
+

+This still requires the method to return the updated slice. We can +eliminate that clumsiness by redefining the method to take a +pointer to a ByteSlice as its receiver, so the +method can overwrite the caller's slice. +

+
+func (p *ByteSlice) Append(data []byte) {
+	slice := *p;
+	// Body as above, without the return.
+	*p = slice;
+}
+
+

+In fact, we can do even better. If we modify our function so it looks +like a standard Write method, like this, +

+
+func (p *ByteSlice) Write(data []byte) (n int, err os.Error) {
+	slice := *p;
+	// Again as above.
+	*p = slice;
+	return len(data), nil)
+}
+
+

+then the type *ByteSlice satisfies the standard interface +io.Writer, which is handy. For instance, we can +print into one: +

+
+	var b ByteSlice;
+	fmt.Fprintf(&b, "This minute has %d seconds\n", 61);
+
+

+Notice that we must pass the address of a ByteSlice +because only *ByteSlice satisfies io.Writer. +The rule about pointers vs. values for receivers is that value methods +can be invoked on pointers and values, but pointer methods can only be +invoked on pointers. This is because pointer methods can modify the +receiver; invoking them on a copy of the value would cause those +modifications to be discarded. +

+

+By the way, the idea of using Write on a slice of bytes +is implemented by bytes.Buffer. +

More to come

@@ -1269,7 +1409,7 @@ is initialized using individual assignments? These questions distract from the important one: what does the code do? Moreover, internal consistency is important not only within a single file, -but also within the the surrounding source files. +but also within the surrounding source files. When editing code, read the surrounding context and try to mimic it as much as possible, even if it disagrees with the rules here. @@ -1279,4 +1419,13 @@ Consistency about little things lets readers concentrate on big ones.

+
+TODO
+verifying implementation
+type Color uint32
+
+// Check that Color implements image.Color and image.Image
+var _ image.Color = Black
+var _ image.Image = Black
+
-->