From 2e5a136e458e6641b09fb3a0db72ce729a9f4f66 Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Sun, 27 Sep 2009 17:59:36 -0700 Subject: [PATCH] add a data section and start populating it with info about allocation, arrays R=rsc DELTA=331 (266 added, 61 deleted, 4 changed) OCL=35024 CL=35030 --- doc/effective_go.html | 333 +++++++++++++++++++++++++++++++++--------- 1 file changed, 268 insertions(+), 65 deletions(-) diff --git a/doc/effective_go.html b/doc/effective_go.html index bdea687f1f..facc86405b 100644 --- a/doc/effective_go.html +++ b/doc/effective_go.html @@ -306,6 +306,11 @@ which is a clear, concise name. 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 +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. Use the package structure to help you choose good names.

@@ -367,7 +372,7 @@ func CopyInBackground(dst, src chan Item) {

-In fact, semicolons can omitted at the end of any "StatementList" in the +In fact, semicolons can be omitted at the end of any "StatementList" in the grammar, which includes things like cases in switch statements:

@@ -711,7 +716,7 @@ of io.ReadFull that uses them well:
 func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
-	for len(buf) > 0 && err != nil {
+	for len(buf) > 0 && err != nil {
 		var nr int;
 		nr, err = r.Read(buf);
 		n += nr;
@@ -721,38 +726,271 @@ func ReadFull(r Reader, buf []byte) (n int, err os.Error) {
 }
 
+

Data

+ +

Allocation with new()

+ +

+Go has two allocation primitives, new() and make(). +They do different things and apply to different types, which can be confusing, +but the rules are simple. +Let's talk about new() first. +It's a built-in function essentially the same as its namesakes +in other languages: it allocates zeroed storage for a new item of type +T and returns its address, a value of type *T. +In Go terminology, it returns a pointer to a newly allocated zero value of type +T. +

+ +

+Since the memory returned by new() is zeroed, it's helpful to arrange that the +zeroed object can be used without further initialization. This means a user of +the data structure can create one with new() and get right to +work. +For example, the documentation for bytes.Buffer states that +"the zero value for Buffer is an empty buffer ready to use." +Similarly, sync.Mutex does not +have an explicit constructor or Init method. +Instead, the zero value for a sync.Mutex +is defined to be an unlocked mutex. +

+ +

+The zero-value-is-useful property works transitively. Consider this type declaration: +

+ +
+type SyncedBuffer struct {
+	lock	sync.Mutex;
+	buffer	bytes.Buffer;
+}
+
+ +

+Values of type SyncedBuffer are also ready to use immediately upon allocation +or just declaration. In this snippet, both p and v will work +correctly without further arrangement: +

+ +
+p := new(SyncedBuffer);  // type *SyncedBuffer
+var v SyncedBuffer;      // type  SyncedBuffer
+
+ +

Constructors and composite literals

+ +

+Sometimes the zero value isn't good enough and an initializing +constructor is necessary, as in this example derived from +package os: +

+ +
+func NewFile(fd int, name string) *File {
+	if fd < 0 {
+		return nil
+	}
+	f := new(File);
+	f.fd = fd;
+	f.name = name;
+	f.error = nil;
+	f.dirinfo = nil;
+	f.nepipe = 0;
+	return f;
+}
+
+ +

+There's a lot of boilerplate in there. We can simplify it +using a composite literal, which is +an expression that creates a +new instance each time it is evaluated. +

+ + +
+func NewFile(fd int, name string) *File {
+	if file < 0 {
+		return nil
+	}
+	f := File{fd, name, nil, 0};
+	return &f;
+}
+
+ +

+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: +

+ +
+	return &File{fd, name, nil, 0};
+
+ +

+The fields of a composite literal are laid out in order and must all be present. +However, by labeling the elements explicitly as field:value +pairs, the initializers can appear in any +order, with the missing ones left as their respective zero values. Thus we could say +

+ +
+	return &File{fd: fd, name: name}
+
+ +

+As a limiting case, if a composite literal contains no fields at all, it creates +a zero value for the type. These two expressions are equivalent: +

+ +
+new(File)
+&File{}
+
+ +

+Composite literals can also be created for arrays, slices, and maps, +with the field labels being indices or map keys as appropriate. +In these examples, the initializations work regardless of the values of EnoError, +Eio, and Einval, as long as they are distinct: +

+ +
+a := [...]string   {Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
+s := []string      {Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
+m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"};
+
+ +

Allocation with make()

+ +

+Back to allocation. +The built-in function make(T, args) serves +a purpose different from new(T). +It creates slices, maps, and channels only, and it returns an initialized (not zero) +value of type T, not *T. +The reason for the distinction +is that these three types are, under the covers, references to data structures that +must be initialized before use. +A slice, for example, is a three-item descriptor +containing a pointer to the data (inside an array), the length, and the +capacity; until those items are initialized, the slice is nil. +For slices, maps, and channels, +make initializes the internal data structure and prepares +the value for use. +For instance, +

+ +
+make([]int, 10, 100)
+
+ +

+allocates an array of 100 ints and then creates a slice +structure with length 10 and a capacity of 100 pointing at the first +10 elements of the array. +(When making a slice, the capacity can be omitted; see the section on slices +for more information.) +In contrast, new([]int) returns a pointer to a newly allocated, zeroed slice +structure, that is, a pointer to a nil slice value. + +

+These examples illustrate the difference between new() and +make(): +

+ +
+var p *[]int = new([]int);       // allocates slice structure; *p == nil; rarely useful
+var v  []int = make([]int, 100); // v now refers to a new array of 100 ints
+
+// Unnecessarily complex:
+var p *[]int = new([]int);
+*p = make([]int, 100, 100);
+
+// Idiomatic:
+v := make([]int, 100);
+
+ +

+Remember that make() applies only to maps, slices and channels. +To obtain an explicit pointer allocate with new(). +

+ +

Arrays

+ +

+Arrays are useful when planning the detailed layout of memory and sometimes +can help avoid allocation but primarily +they are a building block for slices, the subject of the next section. +To lay the foundation for that topic, here are a few words about arrays. +

+ +

+There are major differences between the ways arrays work in Go and C. +In Go: +

+ + +

+The value property can be useful but also expensive; if you want C-like behavior and efficiency, +you can pass a pointer to the array: +

+ +
+func Sum(a *[]float) (sum float) {
+	for _, v := range a {
+		sum += v
+	}
+	return
+}
+
+array := [...]float{7.0, 8.5, 9.1};
+x := sum(&array);  // Note the explicit address-of operator
+
+ +

+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. +

+ + +

Maps

+

Printing

+ +

Methods

+

Basics

+

Pointers vs. Values

+

Methods on arbitrary types

+ +

More to come