Go source code co reading plan - fmt package - Overview & format

Hello World

package main
import "fmt"
func main() {
	fmt.Println("Hello, World")
}

I believe that the first code written by most people engaged in programming is Hello World.
This has also become a tradition. Today, let's start our joint source code reading plan from here.

In the above code, the core part is to call the Println method of fmt package.
Let's take a brief look at what fmt can do

fmt package overview

The official uses two very simple sentences to introduce what this package does. The main function of fmt package is to format the input and output of I/O. It is also pointed out that it is very similar to printf and scanf in C language.
This makes it clear what we can do, one is output and the other is input.

There are 19 function s in the whole fmt package.

func Errorf(format string, a ...interface{}) error
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)
func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)

Remove a special Errorf. They are mainly divided into two parts. One is based on output, and all method names contain print; The other part is mainly input, including scan.

Among them, the output is also divided into two parts. One is to print to standard output or specified io On writer, for example:

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)

Another method is to directly convert the incoming interface into a string and return it to the recipient. For example:

func Sprint(a ...interface{}) string

In addition, there are some small rules. All functions ending in lowercase "f" show that it supports format; All descriptions ending in "ln" are input / output by line.

The use of fmt package is the most familiar part for almost every Go developer. So we won't talk about some examples here.

Next, let's take a look at the structure of the whole fmt code package and some implementations of the core.

fmt package structure

fmt
├── doc.go
├── errors.go
├── errors_test.go
├── example_test.go
├── export_test.go
├── fmt_test.go
├── format.go
├── gostringer_example_test.go
├── print.go
├── scan.go
├── scan_test.go
├── stringer_example_test.go
└── stringer_test.go

You can see that there are only four files in the whole core code except test, example and doc files.

format.go
errors.go
print.go
scan.go

Then let's take a look at format Go

format.go

fmt package is mainly about formatted input and output. Formatting is the basis of input and output.
Then, we have to look at format Go this part.
The main function of this part of the source code is to unify all the verbs used for formatting.

First, the first part is:

type fmtFlags struct {
    .....
}

The purpose of this structure is to format all the verbs used for formatted output into the above structure
Then, fmt is followed by the definition of struct, which is the basis of formatted output such as Printf.

type fmt struct {
	......
	fmtFlags
    ......
}

We can see that fmtFlags mentioned earlier is a part of fmt, which belongs to a classic combination in Go.
The main reason for this independence is that in the official comments, it is mainly to facilitate the rapid initialization of fmtFlags.
The official uses are as follows:

func (f *fmt) clearflags() {
	f.fmtFlags = fmtFlags{}
}

This design is better. We can use it for reference in our own code.

After that, there are three padding for formatting

// writePadding generates n padding bytes directly after f.buf
func (f *fmt) writePadding(n int) {
	if n <= 0 { // No filling required
		return
	}
	buf := *f.buf
	oldLen := len(buf)
	newLen := oldLen + n
	// Open up new memory space for buf according to the number of bytes to be filled and the length of the original buf
	if newLen > cap(buf) {
		buf = make(buffer, cap(buf)*2+n)
		copy(buf, *f.buf)
	}
	// Determines the contents of the bytes of padByte
	// The default is white space. If f.zero is set, it is filled with 0
	padByte := byte(' ')
	if f.zero {
		padByte = byte('0')
	}
	// Fill with. Dbpay padding size space,
	padding := buf[oldLen:newLen]
	for i := range padding {
		padding[i] = padByte
	}
	*f.buf = buf[:newLen]
}

func (f *fmt) pad(b []byte) { }
func (f *fmt) padString(s string) { }

These three are used in combination with each other.
The latter two pads (B [] byte) and padString(s string) are implemented by calling writePadding(n int).
Through the abstraction of, it is more convenient for pad operation, as shown below:

func (f *fmt) pad(b []byte) {
	......
	if !f.minus {
		// Fill on the left
		f.writePadding(width)
		f.buf.write(b)
	} else {
		// Right fill
		f.buf.write(b)
		f.writePadding(width)
	}
}

To achieve formatted output, the most basic thing is to print variables and variable values, so one side is the formatted output of basic data types.

func (f *fmt) fmtBoolean(v bool)
func (f *fmt) fmtUnicode(u uint64)
func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string)
func (f *fmt) fmtFloat(v float64, size int, verb rune, prec int)

These four are Boolean, integer, floating point and Unicode.
These are all relatively basic. The Boolean conversion here is only a few lines of code, so I won't mention it.

For integer conversion, attention should be paid to the accuracy and binary conversion, as shown below:

// fmtInteger formats signed and unsigned integers
func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, verb rune, digits string) {
	......
	prec := 0
	if f.precPresent {
		prec = f.prec
		// A precision of 0 and a value of 0 means "nothing but fill is printed".
		if prec == 0 && u == 0 {
			oldZero := f.zero
			f.zero = false
			f.writePadding(f.wid)
			f.zero = oldZero
			return
		}
	} else if f.zero && f.widPresent {
		prec = f.wid
		if negative || f.plus || f.space {
			prec-- // leave room for sign
		}
	}

	switch base {
	case 10:
		......
	case 16:
		......
	case 8:
		......
	case 2:
		......
	default:
		panic("fmt: unknown base; can't happen")
	}
	......
}

Then there are two methods to operate on string and byte. The main function is to truncate string or byte according to the specified precision.

// truncateString truncates s to the specified length according to the precision of f.prec
func (f *fmt) truncateString(s string) string {
	......
}

// truncate truncates the byte slice b as a string
func (f *fmt) truncate(b []byte) []byte {
	......
}

The rest of the code is based on the further encapsulation of the method mentioned above.

func (f *fmt) fmtS(s string)
func (f *fmt) fmtBs(b []byte)
func (f *fmt) fmtSbx(s string, b []byte, digits string)
func (f *fmt) fmtSx(s, digits string)
func (f *fmt) fmtBx(b []byte, digits string)
func (f *fmt) fmtQ(s string)
func (f *fmt) fmtC(c uint64)
func (f *fmt) fmtQc(c uint64)

format summary

Finally, for this part of the code, we make a summary.
For a more intuitive view, I specially drew a diagram.

This part of the code, I write detailed comments in the source code. It is suggested that after reading the article, you must read it in detail.
You can reply to the official account by keyword format.go get this part of the source code, a total of only 600 lines, will be able to read.
However, there will certainly be more gains.

Tags: Go source code

Posted by omniuni on Sun, 17 Apr 2022 12:50:48 +0930