Channels ▼
RSS

C/C++

Go Tutorial: Object Orientation and Go's Special Data Types


The bufio package provides functions for buffered I/O, including ones for reading and writing strings from and to UTF-8 encoded text files. The io package provides low-level I/O functions — and the io.Reader and io.Writer interfaces will be needed for the americanise program. The io/ioutil package provides high-level file-handling functions. The regexp package provides powerful regular expression support. The other packages (fmt, log, filepath, and strings) were mentioned in the first article in this series.

func main() { 
    inFilename, outFilename, err := filenamesFromCommandLine() 
    if err != nil {
        fmt.Println(err) 
        os.Exit(1)
    } 
    inFile, outFile := os.Stdin, os.Stdout  
    if inFilename != "" {
        if inFile, err = os.Open(inFilename); err != nil { 
            log.Fatal(err)
        } 
        defer inFile.Close() ➍
    }
    if outFilename != "" {
        if outFile, err = os.Create(outFilename); err != nil { 
            log.Fatal(err)
        } 
        defer outFile.Close() ➎
    }
    if err = americanise(inFile, outFile); err != nil { 
        log.Fatal(err) 
    } 
}

The main() function gets the input and output filenames from the command line, creates corresponding file values, and then passes the files to the americanise() function to do the work. The function begins by retrieving the names of the files to read and write and an error value. If there was a problem parsing the command line, we print the error (which contains the program's usage message), and terminate the program. Some of Go's print functions use reflection (introspection) to print a value using the value's Error()string method if it has one, or its String() string method if it has one. If we provide our own custom types with one of these methods, Go's print functions will automatically be able to print values of our custom types.

If err is nil, we have inFilename and outFilename strings (which may be empty), and we can continue. Files in Go are represented by pointers to values of type os.File, so we create two such variables initialized to the standard input and output streams (which are both of type *os.File). Since Go functions and methods can return multiple values, it follows that Go supports multiple assignments such as those we have used here.

Each filename is handled in essentially the same way. If the filename is empty, the file has already been correctly set to os.Stdin or os.Stdout (both of which are of type *os.File — a pointer to an os.File value representing the file); but if the filename is nonempty, we create a new *os.File to read from or write to the file as appropriate. The os.Open() function takes a filename and returns an *os.File value that can be used for reading the file. Correspondingly, the os.Create() function takes a filename and returns an *os.File value that can be used for reading or writing the file, creating the file if it doesn't exist, and truncating it to zero length if it does exist. (Go also provides the os.OpenFile() function to exercise complete control over the mode and permissions used to open a file.) The os.Open(), os.Create(), and os.OpenFile() functions return two values: an *os.File and nil if the file was opened successfully, or nil and an error if an error occurred.

If err is nil, we know that the file was successfully opened so we immediately execute a defer statement to close the file. Any function that is the subject of a defer statement must be called — hence, the parentheses after the functions' names (➍,➎) — but the calls only actually occur when the function in which the defer statements are written returns. So the defer statement "captures" the function call and sets it aside for later. This means that the defer statement itself takes almost no time at all and control immediately passes to the following statement. Thus, the deferred os.File.Close() method won't actually be called until the enclosing function — in this case, main() — returns (whether normally or due to a panic, which will be discussed shortly), so the file is open to be worked on and yet guaranteed to be closed when we are finished with it, or if a panic occurs.

If we fail to open the file, we call log.Fatal() with the error. This function logs the date, time, and error (to os.Stderr unless another log destination is specified), and calls os.Exit() to terminate the program. When os.Exit() is called (directly, or by log.Fatal()), the program is terminated immediately and any pending deferred statements are lost. This is not a problem, though, since Go's runtime system will close any open files, the garbage collector will release the program's memory, and any decent database or network that the application might have been talking to will detect the application's demise and respond gracefully. Just the same as with the bigdigits example in my previous article, we don't use log.Fatal() in the first if statement, because the err contains the program's usage message and we want to print this without the date and time that the log.Fatal() function normally outputs.

In Go, a panic is a runtime error (rather like an exception in other languages). We can cause panics ourselves using the built-in panic() function, and can stop a panic in its tracks using the recover() function. In theory, Go's panic/recover functionality can be used to provide a general-purpose exception handling mechanism — but doing so is considered to be poor Go practice. The Go way to handle errors is for functions and methods to return an error value as their sole or last return value — or nil if no error occurred — and for callers to always check the error they receive. The purpose of panic/recover is to deal with genuinely exceptional (that is, unexpected) problems and not with normal errors. (Go's approach is very different from C++, Java, and Python, where exception handling is often used for both errors and exceptions. A more detailed discussion and rationale for Go's panic/recover mechanism is available. With both files successfully opened (the os.Stdin, os.Stdout, and os.Stderr files are automatically opened by the Go runtime system), we call the americanise() function to do the processing, passing it the files on which to work. If americanise() returns nil, the main() function terminates normally and any deferred statements — in this case, ones that close the inFile and outFile if they are not os.Stdin and os.Stdout — are executed. And if err is not nil, the error is printed, the program is exited, and Go's runtime system closes any open files.

The americanise() function accepts an io.Reader and an io.Writer, not *os.Files, but this doesn't matter since the os.File type supports the io.ReadWriter interface (which simply aggregates the io.Reader and io.Writer interfaces) and can therefore be used wherever an io.Reader or an io.Writer is required. This is an example of duck typing in action — the americanise() function's parameters are interfaces, so the function will accept any values — no matter what their types — that satisfy the interface; that is, any values that have the methods the interfaces specify. The americanise() function returns nil, or an error if an error occurred.

func filenamesFromCommandLine() (inFilename, outFilename string,
    err error){
    if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
        err = fmt.Errorf("usage: %s [<]infile.txt [>]outfile.txt",
            filepath.Base(os.Args[0]))
        return "", "", err
    }
    if len(os.Args) > 1 {
        inFilename = os.Args[1]
        if len(os.Args) > 2 {
            outFilename = os.Args[2]
        }
    }
    if inFilename != "" && inFilename == outFilename {
        log.Fatal("won't overwrite the infile")
    }
    return inFilename, outFilename, nil
}

The filenamesFromCommandLine() function returns two strings and an error value — and unlike the functions we have seen so far, here the return values are given variable names, not just types. Return variables are set to their zero values (empty strings and nil for err in this case) when the function is entered, and keep their zero values unless explicitly assigned to in the body of the function. (More on this shortly.) The function begins by checking whether the user has asked for usage help. (The Go standard library includes a flag package for handling command-line arguments. Third-party packages for GNU-compatible command-line handling are available from godashboard.appspot.com/project.) If they have, we create a new error value using the fmt.Errorf() function with a suitable usage string, and return immediately. As usual with Go code, the caller is expected to check the returned error and behave accordingly (and this is exactly what main() does). The fmt.Errorf() function is like the fmt.Printf() function we saw earlier, except that it returns an error value containing a string using the given format string and arguments rather than writing a string to os.Stdout. (The errors.New() function is used to create an error given a literal string.)


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video