kilabit.info
Build | GitHub | Mastodon | SourceHut |

This is my notes on learning Go language while developing dsv [1]. My background is C/C++ with a little Java.

Range is fast

The question is this: does accessing slice using element is slower than using index? Discussion on Golangbridge forum [2].

What do I mean by accessing by element is this,

slice := []int{0,1,2,3,4,5,6,7,8,9}

for k,v := range slice {
...
}

v is an value of slice. While accessing by index, is like an usual array access slice[i].

Logically (if you are new and not Go main developer) when accessing slice using range, a copy of slice element is passed to temporary variable v, subsequently assuming that a copy process will take some operations in the background. While accessing by index is just take the address at index i.

Turn out, I was false.

Here is the benchmark file, and here is the output,

BenchmarkSumByIndex10000-8         50000             29073 ns/op           81922 B/op          1 allocs/op
BenchmarkSumByElm10000-8           50000             28527 ns/op           81921 B/op          1 allocs/op
BenchmarkSumByIndex1000000-8        1000           2077027 ns/op         8003657 B/op          8 allocs/op
BenchmarkSumByElm1000000-8          1000           2005368 ns/op         8003659 B/op          8 allocs/op

Accessing slice element with range is little bit faster than accessing by index.

Range is mutable

This is a rookie mistake.

When using range on slice of struct, each element passed on range is mutable. Here is the Go playground.

Do not embed struct if you want other to extend it

Or how to write generic in Go.

Coming from strong object oriented background, the way on how generic works in Go give me a lot of rewriting on dsv package.

Use case: You have struct reader and struct data to save content that the reader read from file, but you want another user to use their implementation, through data interface, to be used in your reader.

Wrong way to do this is by embeding data in reader

type Data struct {
	v int
}

type Reader struct {
	Data
}

because you think that user can access methods in Data through reader easily.

Right way to do this is using interface separating the job between reader and the data,

type DataInterface interface {
	// methods operate with data
}

type Data struct {
	// fields of data
}

type Reader struct {
	data interface{}
}

func NewReader(mydata interface{}) *Reader {
	if mydata == nil {
		mydata := &Data{
			// Set default data value.
		}
	}

	return &Reader{
		data: mydata
  	}
}

func (reader *Reader) Data() interface{} {
	return reader.data
}

So, when you need the data in the reader use the DataInterface,

func (reader *Reader) Read() {
	di := reader.GetData().(DataInterface)
}

Interface should only contain methods that operate with fields

Given an generic object Car with two fields: model and max-speed, the methods that should included in CarInterface should only methods that operate on the fields (e.g. initialization, getter, setter, etc).

type Car struct {
	model string
	maxSpeed int
}

type CarInterface interface {
	Init()
	Model() string
	SetModel(string)
	MaxSpeed() int
	SetMaxSpeed(int)
}

Everything else (features for car) should be written using parameter with CarInterface,

func MoveSlowly(ci CarInterface) {
	// make the car move slowly.
}

Why? I will leave it to the reader to answer it.

Slice is struct

Most of the time, it does not matter wether you want to pass slice to function by references or values, both will change the content of slice. But …​ if you have quite big number of element in slice, passing by pointer will make it faster.