Golang
Getting Started with Go course notes by Oliver Frolovs, 2020.
- Not a beginner’s course! It assumes a good understanding of basic principles of programming.
- I watched the videos at 1.25 speed and skipped some parts, skim-reading the transcripts instead.
- Good choice of topics and level of depth. Pointers covered before more pedestrian data types.
- The scope of variables (W2) is explained with the right balance of formalism and accessibility. And very concisely.
Setting up the environment
I use Ubuntu 20.04 LTS under WSL and the Go version in Ubuntu apt
repo was too old for my liking.
Installing Go
I followed the Download and Install instructions on the Go website to install a more recent version.
$ curl -O https://dl.google.com/go/go1.15.2.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.15.2.linux-amd64.tar.gz
$ export PATH=$PATH:/usr/local/go/bin
I made a Go workspace ($GOPATH
), although see the note below on modules.
$ mkdir ~/go
I’ve also updated my interactive shell configuration file .bashrc
:
# Go as installed using official installation method
if [ -x /usr/local/go/bin/go ]; then
# Go binaries
export PATH=$PATH:/usr/local/go/bin
# Go workspace
export GOPATH=~/go
fi
Some points for further investigation:
- FIXME
curl
ing the download URL from the webpage returned a piece of HTML with the a different download URL… weird! - FIXME shouldn’t
/usr/local/
be writeable withoutsudo
? - TODO there is a way to
dpkg
thetar ...
part and make it a proper (local) package in Ubuntu - TODO modules made
$GOPATH
somewhat irrelevant: Using Go modules
Setting up Visual Studio Code
I’ve added VSCode Go extension to my WSL Remote Target. I would like to use modules, so following the advice given during installation, I’ve enabled Go Language Server
option by setting go.useLanguageServer
to true
in Go extension settings.
This has prompted to install gopls
to which I have agreed.
Once I opened some .go
file, VSCode has asked me to install the missing modules. I responded with Install All
.
I was able to run test
in one of the [REDACTED] unit tests, which I accept as a sign of success in setting up the environment.
Code organisation
I started out using Go workspace with the intention to move to module-based workflow later.
My Go workspace ($GOPATH
) is set to ~/go
and structured according to the official convention into:
$GOPATH/bin
$GOPATH/pkg
$GOPATH/src
Say, I wanted to have a Go “project” for experimentation. I name it go-study
and place it in $GOPATH/src/go-study/
. There is a file main.go
there, with the following code.
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("Hello, Gruffalo!")
fmt.Printf("%f", math.Trunc(1.98))
}
This way, when I add a new entry to import
, VSCode does not complain about missing libraries, so I guess it’s OK?
Update: actually, I don’t even need to add anything to import
because IntelliSense manages the import based on what’s actually used in the .go
file. The import
statement gets updated on file save. Neat.
Test Go installation
Separately from VSCode, I test if Go runs from the WSL Windows Terminal.
$ go version
go version go1.15.2 linux/amd64
Liftoff!
This concludes the yak shaving part and I can finally begin the course.
Why Go
- Fast
- Garbage collection (gc code gets compiled together with your code)
- Simpler objects (unlike C++, Go is weakly object-oriented)
- Concurrency built-in
- Extensive standard library
User-defined types
Group together data and code. Go does not use the term class. Instead, it has struct.
- It doesn’t have inheritance.
- It doesn’t have constructors.
- It doesn’t have generics.
Hmm…
Concurrency
Most, but not all, motivation stems from performance limits.
Moore’s Law doesn’t work anymore. Can’t crank up the clock anymore — power constraints are real. We are at the limits of air cooling. Solution? Increase the number of cores.
Concurrent programming enables parallelism.
Concurrency in Go
Go has a lot of concurrency primitives built into the language.
- routines represent concurrent tasks, basically a thread
- channels are used for communication between concurrent tasks (routines?)
These are the high level yet basic keywords in the language. The concurrency is built into the language.
Code organisation in Go
Workspaces
A workspace is a directory. There is typically a hierarchy of directories inside the workspace.
Recommended to have inside the workspace directory:
src
contains source code files.pkg
contains packages (libraries).-
bin
contains executables (compiled programs). - A programmer typically has one workspace for many projects.
- The directory hierarchy is recommended but not enforced. Common organisation is good for sharing.
- The workspace directory is defined by
$GOPATH
environment variable.
The $GOPATH
may be defined by the Go installer, but don’t rely on it. The recommended installation method on Linux un-tar
-ring into /usr/local/
certainly cannot do that.
Packages
A package is a group of related source code files. Each package can be imported by other packages.
The import
keyword is used to access other packages. The standard library include many packages. The $GOROOT
and $GOPATH
directories are searched for packages.
import "fmt"
import (
"cobra"
"fmt"
"math"
)
There must be one package named main
. When it’s build (compiled), an executable is made. When other packages are built, an executable is not made.
package main
import "fmt"
func main() {
fmt.Printf("Hello, Gruffalo!\n")
}
FIXME I suspect that packages are searched differently in the context of using modules.
The go
tool
The tool can do many different things.
go build
compiles the program. Arguments can be a list of packages or a list of.go
files.go doc
prints documentation for a package.go fmt
formats the code in the canonical way.go get
downloads and installs packages.go list
lists all installed packages.go run
compiles and runs the executable. Update: I <3 it!go test
runs tests saved in files ending in_test.go
Variable declarations
Follow the format: var name type
var x int
var s string
Basic types: integers, floats, strings.
Type declarations
Aliases can be defined for type names. Useful for writing self-documenting code.
type Celsius float64
type IDNum int
Then variables can be declared using these aliases.
var growboxTemp Celsius
var pid IDNum
Go linters don’t like underscores ( _
) in variable names. Use capitalisation instead: tempDiff
, not temp_diff
.
Variable initialisation in Go
Initialise after declaration
We’ve seen that many times before in C.
var x int
x = 100
Initialise in declaration
The literal constant 100
is an integer, so the type is inferred as integer.
var x = 100
Type inference is optional. The type can be specified as well.
var x int32 = 100
Uninitialised variables have a zero value. I suppose the zero value is different for each type.
var x int // x == 0
var s string // s == ""
TODO what’s the zero value of a custom type?
Short variable declaration
Declare and initialise in one line using the :=
operator.
x := 100
The variable type is inferred from the expression on the right hand side. Can do this only inside a function.
Pointers in Go
A pointer value is an address in memory. So, a pointer points to some data in memory.
Operations on pointers:
- The
&
operator returns the address of a variable or a function. - The
*
operator returns data at an address. This is known as dereferencing.
They are the opposites of one another.
var x int = 1
var y int
var ip *int // ip is a pointer to int
ip = &x // ip now points to x
y = *ip // y is now 1
Now y
has value 1
.
FIXME What if x
is assigned a different value now?
Creating variables with new()
The new()
function creates a variable and returns a pointer to it. The variable is initialised to zero by default.
ptr := new(int)
*ptr = 42
A variable of type int
is created and the pointer to it is returned. The pointer is then dereferenced, that is the value 42
is put into the memory location pointed to by ptr
.
Variable scoping
In Go, variable scoping is done using blocks — they are a sequence of declarations and statements within matching brackets, {}
{
// scope A
{
// scope B
// variables declared in scope A are also accessible
}
// scope A only
}
There is also a hierarchy of implicit blocks:
- Universe block – all Go source code, the biggest block.
- Package block – all source code in a particular package. The package can be composed of many files. This block is a subset of the Universe block.
- File block – all source code in the current file. This block is a subset of the Package block.
if
,for
, andswitch
define implicit blocks as well.- individual clauses in
switch
orselect
each get a block.
The main point is that there is a hierarchy of blocks. At each level the block can have its own environment of variables associated with it.
Using the set notation, {clauses in switch
or select
} ⊆ {if
, for
, switch
} ⊆ File ⊆ Package ⊆ Universe.
Lexical scoping
This defines how variables references are resolved. Go is lexically scoped using blocks.
I’ll just say that it’s “intuitive” based on the model I learned from C <3 – go to the greater including scope, until the variable definition is found.
Traditional view on memory management
Stack vs heap
Traditionally, the stack is where the function local variables are allocated. They are deallocated once the function completes (returns).
The heap is a persistent area of memory which does not get deallocated automatically. Memory on the heap must be allocated and deallocated manually when it’s no longer needed.
In C <3:
x = malloc(32);
// the variable x has a life
// full of adventure and suffering...
free(x);
It’s error-prone but fast.
Go does things differently. It has garbage collection.
Garbage collection
When using pointers, it’s very hard to figure out when a variable is no longer in use so it can be deallocated safely.
Example in Go, returning the pointer to a local variable. This is not legal in C, but Go can do it:
func foo() *int {
x := 1
return &x
}
func main() {
var y *int
y = foo()
fmt.Printf("%d", *y)
}
This is possible thanks to garbage collection.
Go is a compiled language which enables garbage collection!
- Implementation is fast
- Compiler determines stack vs heap
- Garbage collection in the background
FIXME Does GC pause the program?! I remember Azul Java GC does not, so it’s possible.
Comments in Go
Like in C <3
Single-line comments
// Mamka Tvoya
var x int // Боже Праведный!
Block comments
/*
Mamka Tvoya
Иисус Христос был русским и родился в Твери.
2020 (с) Православная Тверь
*/
var x int
Output
Using fmt
package:
fmt.Printf()
fmt.Println()
String concatenation
x := "Gruffalo"
fmt.Printf("Hello, " + x)
Format strings
Very similar to C <3 Use a conversion character for each argument.
x := "Gruffalo"
fmt.Printf("Hello, %s", x)
Integers in Go
Generic integer declaration is int
:
var x int
But there is a wide variety of integer types, based on width in bits and sign:
int8
,int16
,int32
,int64
uint8
,uint16
,uint32
,uint64
— unsigned
Finally (!) some sensible names, no more short
, and long long
, haha.
Usually it’s best to declare as int
and leave it to the compiler to figure out, unless there is a reason to control the size manually.
Operations on integers
As one would expect in C <3
Type conversions in Go
Type conversions are not always possible. The following would fail (thankfully).
var x int32 = 1
var y int16 = 2
x = y
The compiler views int32
and int16
as different types so the assignment would fail.
T()
operator
Convert type with T()
operator.
var x int32
var y int16 = 2
x = int32(y)
Converting int16
to int32
is always possible — it sign-extends the value in y
.
Not all conversions are this easy. FIXME more information?
Floating point numbers
float32
~6 decimal digits of precisionfloat64
~15 decimal digits of precision
Floating-point numbers can be expressed as decimals or using scientific notation.
var g float64 = 9.81 // acceleration of free fall on Earth [m/s^2]
var h float64 = 6.62607e-34 // Plank's constant [J.s]
Complex numbers in Go
Rejoice, mortals! Natively supported, the complex numbers are.
var z complex128 = complex(2,3)
FIXME is this some kind of constructor? But Go doesn’t have them, right?!
ASCII and Unicode
Characters are not the same as bytes (and they haven’t been for a long time).
- In ASCII, 7 or 8-bit code represents a character, so each character can be packed into a byte. But that covers only
2^8 = 256
characters, so not a lot.
Unicode characters are 32-bit, so can represent a lot more characters.
UTF-8 is a variable Unicode encoding which matches ASCII for lower 2^8 code points. It is the default encoding in go.
Code point is the Unicode term for a character. There can be 2^32
code points. In Go, the code point is called a rune.
Strings in Go
- Strings are an arbitrary sequence of bytes.
- Strings are read-only.
- Strings are in UTF-8, so at higher level they are made of runes.
- Strings are often meant to be printed.
String literals
Are notated by double quotes.
x := "Gruffalo"
Bytes are not runes!
The string in the following example is made of Cyrillic runes. Each of these runes takes more than a single byte. Thus, byte-addressing the string prints rubbish and not the corresponding runes.
x := "Православная Тверь!\n"
fmt.Printf("%c", x[0])
fmt.Printf("%c", x[3])
Outputs ASCII symbols Ð
, instead of runes П
and в
.
String packages in Go
Working at individual rune level
Given that bytes are not equivalent to runes, for working at the runes level (of abstraction), the Unicode
package treats strings as “arrays of runes”.
- Runes are divided into many different categories.
-
The package provides a set of predicates to test categories of runes
IsDigit(r rune)
IsSpace(r rune)
IsLetter(r rune)
IsLower(r rune)
IsPunct(r rune)
- …
-
The package provides functions for conversions
ToUpper(r rune)
ToLower(r rune)
- …
Working at the whole string level
There is also the strings
package. It provides functions to manipulate UTF-8 encoded strings. It does not look at individual runes, but at the whole string instead.
- Search
- Compare
- Contains
- Has prefix
- Index
- …
String manipulation
Strings are immutable, but the strings
package provides some functions to create new strings based on some existing string.
- Replace
- To lower and to upper
- Trim spaces
Another useful package is strconv
containing conversions to and from other basic datatypes.
Atoi(s)
converts string to an integerItoa(i)
converts integer to a stringFormatFloat(f, fmt, prec, bitSize)
converts fp number to a string representation of that numberParseFloat(s, bitSize)
does the opposite — converts a string to a floating point number- …
Constants in Go
A constant is an expression whose value is known at compile time. A type is inferred from the right-hand side of the assignment: boolean, string, number.
const x = 1.3
const (
y = 4
z = "Gruffalo"
)
Enumerations in Go
iota
generates a set of related but distinct constants.-
Often represents a property which has several distinct possible values.
- days of the week
- months of the year
- the key thing is: constants must be different, but the actual value is not important
Just like an enumerated type in other languages, like in C <3
FIXME Why did they pick up an unfamiliar name?!
type Grades int
const (
A Grades = iota
B
C
D
F
)
In these example, we want five different grades, represented by integers, but we don’t care what actual value each grade has, we just want something different.
We don’t have to repeat the type and iota
, it is implied! Each constant is assigned to a unique integer.
Control flow in Go
The control flow describes the order in which the statements are executed inside a program.
The most basic control flow
Is executing one statement at a time, one after another, top to bottom in terms of mapping it to the source code file.
Control flow might change from the basic one because the programmer inserted statements which alter the control flow.
Conditional statement
if <condition> {
<consequent>
}
Unlike C <3, the brackets {}
are a must. There is also an optional else
clause, as one would expect.
Iteration
for
loops iterate while a condition is true.- May have an initialisation and update operations.
for <init>; <cond>; <update> {
<stmts>
}
There are several forms of for
statements. The three most common ones are:
// Traditional form, seen before
for i:=0; i<10; i++ {
fmt.Printf(".")
}
// The for loop itself has no initialisation and no update, only the condition
i = 0
for i < 10 {
fmt.Printf(".")
i++
}
// Infinite loop, maybe for an embedded system?
for {
fmt.Printf(".")
}
Switch/case statement
switch
is a multi-way if statement.- it may contain a
tag
which is a variable to be checked. - only one
case
is going to be executed. This is a deviation from C <3.
switch x {
case 1:
// ...
case 2:
// ...
default:
// optional part if no other case match
}
Note, that break
is not necessary to skip other cases, this is not C <3.
Tagless switch
Sometimes one does not want a tag for each case, but an expression instead. The first case for which the expression evaluates to true
is executed.
The case contains a boolean expression to evaluate.
switch {
case x > 1:
// ...
case x < -1:
// ...
default:
// optional
}
A tagless switch can be used in place of a sequence of chained if else
statements. And this looks good, I must add! Chained if-else never looked right.
Break and continue
break
exits the containing loop, as one would expect in C <3.continue
skips the rest of the current iteration to the next iteration.
Reading user input in Go
Most trivial way
The scan
function is the most basic way for reading user input. It is in fmt
package.
- takes a pointer as an argument
- typed data is written to pointer
- returns the number of scanned items and the error code, or
nil
if all is well
var appleNum int
fmt.Printf("Number of apples?")
num, err := fmt.Scan(&appleNum)
fmt.Printf(appleNum)
Note the multiple assignment!
More sophisticated: handling whole lines
If the input is supposed to have whitespace, best to use bufio
.
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("/path/to/file.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
From StackOverflow
I also liked another answer, with different options considered.
Composite datatypes in Go
Aggregate data types we’ll look at: arrays, slices, maps, and structs.
Arrays in Go
Fixed-length sequence of elements of a chosen type.
Key points:
- The size of the sequence is fixed.
- The elements are all of the same type.
As in C, indixing starts at 0. Elements are accessed using subscript notation []
. Unlike C, the arrays are initialised to zero by default.
var x [5]int
x[0] = 2
fmt.Printf(x[1]) // is going to be zero
Array literals in Go
Is a sequence of pre-defined values that make up an array.
var x [5]int = [5]{1, 2, 3, 4, 5}
The left-hand side declaration is familiar. The right-hand side syntax is new. This is how you describe array literals. I note that the size of the literal is explicitly provided, which is great. Length of the literal must be length of array.
The size of the array can be inferred from the size of array literal using …
keyword
x := [...]int {1, 2, 3, 4}
Iterating through array in Go
Using for
loop.
x := [3]int {1, 2, 3}
for i, v range x {
fmt.Printf("index %d, value %d \n", i, v)
}
range
is a keyword. It returns two values: index and element value at that index.
Slices in Go
A slice is a window on an underlying (possibly larger) array. They can be variable size, up to a the size of the whole array. A slice has three main properties:
- a pointer indicating the start of the slice
- the length, which is the number of elements in the slice
- the capacity, which is the maximum number of elements in the slice — from start of slice to end of array.
arr := [...]string {"a", "b", "c", "d", "e", "f", "g"}
s1 := arr[1:3] // includes elements 1 and 2: [b,c]
s2 := arr[2:5] // includes elements 2,3, and 4: [c,d,e]
Note, that slices can overlap. We use bracket and colon notation with slices. Similar to Python’s slices so far.
Length and capacity
For slices,
len()
returns the length, aka sizecap()
returns the capacity (of an underlying array)
FIXME I got into the habit of always adding of an underlying array when I say capacity, at least until I remember the Go terminology.
Referring to slices
When writing to a slice, you are writing into the underlying array. Note, that different slices can be overlapping on the underlying array.
Slice literals
- Can be used to initialise the slice.
- Creates the underlying array and creates a slice to reference it.
- Slice points to the start of the array, length is capacity.
Note, that there is nothing in the brackets []
, so the compiler infers that this is a slice. It creates the array and it makes the slice point to the beginning of that array.
sli := []int {1, 2, 3}
FIXME This looks like rather technical shenanigans which have to do with the language being a compiled one? Would like to see some uses of this which is not just like what you do with Python slices (with added technical complexity).
Creating slices
There is a function make()
which can be used to create slices (and an underlying array).
The use case for it is when you want to make a slice because you have some data to store and you want to initialise the slice to a particular size in the beginning.
Two argument version: specify type and length or capacity:
// Initialise a slice to zero. Length == capacity == 10.
sli = make([]int, 10)
Three argument version: specify length and capacity separately. So, the underlying array is bigger than the slice:
// Initialise a slice to zero. Length is 10, but capacity is 15.
sli = make([]int, 10, 15)
Increasing the size of a slice
Use append()
to increase the size (length) of slice. It adds element to the end of the slice.
If the size of the underlying array is reached, it will make a new array. There is a time penalty for that.
sli = make([int, 0, 3]) // slice length is zero, but underlying array is size 3
sli = append(sli, 100) // the underlying array is increased to size 100
Hash tables
Allow fast access to large bodies of data. Contains key/value pairs. Each value is associated with unique key. Hash function is used to compute the slot for a key. All of this is familiar territory.
Tradeoffs of hash tables
Advantages of hash tables over lists:
- Faster lookups than lists (O(1) rather than O(n), I must add)
- Arbitrary keys, not just integers (but key must be immutable, I must add). Those keys may have some meaning to them, unlike simple integers.
Disadvantages of hash tables over lists:
- May have collisions, when two keys hash to same slot. Unlikely, and handled by Go.
- I would add, that classic hash tables are not ordered, while the lists are. This may be a disadvantage in many scenarios.
Maps in Go
A map is a Golang’s implementation of a hash table.
Creating maps
- Use
make()
to create a map. The following example declares, and then creates a map.
var idMap map[string]int // [key type]value type
idMap = make(map[string]int)
- Use a map literal to create a map.
idMap := map[string]int {
"joe": 123
}
Accessing maps
Reference a value with [key]
. Returns zero if key is not present.
FIXME i don’t like this, zero is a valid integer value, wtf?
fmt.Println(idMap["joe"])
Adding a key/value pair
idMap["mouse"] = 456
Deleting a key/value pair
delete(idMap, "joe")
Key existence test
Two-value assignment tests for existence of the key:
id, p := idMap["joe"]
A boolean p
would indicate if the key "joe"
is in the map.
Number of values in the map
The len
function returns the number of key/value pairs in the map.
fmt.Println(len(idMap))
Iterating through a map
Use two-value assignment with a range
keyword.
for key, val := range idMap {
fmt.Println(key, val)
}
No surprises here.
Structs in Go
A struct
is short for structure and it comes from C <3. It’s an aggregate data type, grouping together values of arbitrary data types.
Example: Person struct
. Each person
has name, address, and a phone no.
- Have three separate variables,
name
,address
,phone
; or - Make a single
struct
with three fields.
Can define a struct
type, analogous to type Celsius int
seen before.
type Person struct {
name string
addr string
phone string
}
var p1 Person // contains data one person
var p2 Person // contains data for another person
Accessing struct
fields
Use dot notation.
p1.name = "Gruffalo"
x := p1.addr
Initialising struct
Use new()
:
p1 := new(Person)
FIXME Why not make()
?
Or use a struct literal:
p1 := Person{name: "Gruffalo", addr: "Deep Dark Wood", phone: "123"}
TODO Revision examples
- The capacity is from the pointer to the slice to the end of an underlying array!
append(xs, 42)
adds the value 42 to the slice, expanding the underlying array, if the need be, socap(xs)
increases.
Syntax
This example puts together quite a few concepts from Go syntax.
TODO annotate with comments
type P struct {
x string
y int
}
func main() {
b := P{"x", -1}
a := [...]P{
P{"a", 10},
P{"b", 2},
P{"c", 3}
}
for _, z := range a {
if z.y > b.y {
b = z
}
}
fmt.Println(b.x)
}
Length and capacity
The following prints 0 3
.
append
puts the item into the slice, or expands the slice if the need be.
func main() {
s := make([]int, 0, 3)
s = append(s, 100)
fmt.Println(len(s), cap(s))
}
Protocols and formats in Go
Golang has packages to handle lots of different protocols and data formats.
RFC
Not news, just not something I check out a lot in the recent years))
- A bit of history: RFC1866 HTML2.0
- URI
- HTTP
Network protocols packages
net/http
, for HTTP, such as `http.Get(“www.cloud84.dev”)net
for TCP/IP and sockets programming
net.Dial("tcp", "uci.edu:80")
It’s very useful to have these packages ready for use.
JSON
- JavaScript Object Notation
- RFC7159
- Format to represent structured information
- Human-readable
- All Unicode
- Fairly compact representation (for a human-readable format)
- Attribute-value pairs correlate to
struct
ormap
in Golang - Basic value types: bool, number, string, array, object
- Types can be combined recursively:
- array of struct
- struct in struct
- and more…
A struct
in Go:
p1 := Person{name: "Gruffalo", addr: "Deep Dark Wood", phone: "123"}
As a JSON object:
{
"name": "Gruffalo",
"addr": "Deep Dark Wood",
"phone": "123"
}
JSON marshalling
Generating JSON representation from an object.
I remember, that marshalling and serialisation aren’t exactly the same thing!
A type definition in Go:
type Person struct {
name string
addr string
phone string
}
Define a value of that type using field-value initialisers
p1 := Person{name: "Gruffalo", addr: "Deep Dark Wood", phone: "123"}
Note {}
in the definition.
Could have used value initialisers only:
p1 := Person{"Gruffalo", "Deep Dark Wood", "123"}
Note, that field-value initalisers, and value initialisers cannot be mixed, that is either all or no field names must be provided.
Marshall into JSON format:
barr, err := json.Marshall(p1)
json.Marshall()
returns JSON representation as []byte
.
barr
is data as[]byte
, sobarr
stands for byte arrayerr
isnil
if there is no conversion error
JSON unmarshalling:
Converting a JSON []byte
into a Go object.
var p2 Person
err := json.Unmarshall(barr, &p2)
- as before,
err == nil
if there is no conversion error
Constraint: p2
must “fit” the JSON []byte
, that is must have the same attributes/fields.
File access in Go
Files are still commondly used to trade data between the programmes. File access is linear access (not random access), because originally they come from tapes.
(What a crap abstraction! Obsolete af.)
Basic operations:
- Open
- Read bytes into
[]byte
- Write
[]byte
into file - Seek moves read/write head (eek! obsolete)
Go has ioutil
package for basic file-related operations.
Reading files in Go
To read, use ioutil.ReadFile(fname)
.
- Don’t need to open/close, as the whole file is read at once.
- Will not work with large files if cannot fit them in memory.
Writing files in Go
dat = "Hello, Gruffalo!"
err := ioutil.WriteFile("outfile.txt", dat, 0777)
- Writes
[]byte to file
- Creates a file
- Uses UNIX-style access permission
- Cannot append to an existing file
os
package file access
So, ioutil
gives some very basic file-related functions. For more granular control, the os
package has file-related functions.
os.Open()
opens a file and returns a descriptorFile
os.Close()
closes a file descriptoros.Read()
reads from a file into a[]byte
, controls the amount read
Opening and reading example
Using the os
package for opening and reading files.
f, err := os.Open("dt.txt")
barr := make([]byte, 10)
nb, err := f.Read(barr)
f.Close()
- Reads and fills
barr
Read
returns # of bytes read- May be less than
[]byte
length
Writing a file example
Using the os
package for creating the file and writing data into it.
f, err := os.Create("outfile.txt")
barr := []byte {1,2,3}
nb,err := f.Write(barr)
nb,err := f.WriteString("Hello, Gruffalo!") // it has to be Unicode sequence
We could have appended the data to an existing file as well, os
has functions for that.
The end
This concludes the course!