I'm very new to Go and I've read (from the FAQ) that Go is both OO and not. I'd like to create data structures using Structs and find myself trying to think of Structs as simple objects. My canonical proof of concept is building a Car. I understand that a car is a real world object so it lends itself to OOP, which might be weird in Go. But I imagine that a User class would be equally convenient so this will be a useful learning exercise and reference for me.
This example compiles but does not function properly. It uses multiple source files so you'll have to manipulate your GOPATH and create a project folder for this.
It should look like this:
$GOPATH/src/car/car.go
$GOPATH/src/car/parts/engine.go
Or another way to look at it:
$ cd /tmp/go/src
$ tree
.
└── car
├── car.go
└── parts
└── engine.go
Main asks the car instance to .Start() below. When it comes back to main, car is not started.
/* car/car.go */
package main
import (
"car/parts"
"fmt"
)
type Car struct {
sMake string
model string
engine parts.Engine
}
func init() { // optional init of package
// note that we can't use this as a constructor?
}
func main() {
car := Car{
sMake: "AMC",
model: "Gremlin",
}
fmt.Printf("I'm going to work now in my %s %s
", car.sMake, car.model)
fmt.Println("I guess I should start my car.")
car.Start()
fmt.Println("Engine started?", car.engine.IsStarted())
// fail -- engine started is false :(
}
func (car Car) Start() {
fmt.Println("starting engine ...")
car.engine.Start()
fmt.Println("you'd think it would be started here ...", car.engine)
// but it's not
}
Splitting up the source files is convenient. All of this works
/* car/parts/engine.go */
package parts
import (
"fmt"
)
type Engine struct {
cylinders int
started bool
}
func (engine Engine) Start() {
fmt.Println("Inside the Start() func, started starts off", engine.started)
engine.started = true
fmt.Println("Inside the Start() func, then turns to", engine.started)
// this is a sanity check
}
func (engine Engine) IsStarted() bool {
return engine.started
}
Running this outputs:
$ go run car.go
I'm going to work now in my AMC Gremlin I guess I should start my car. starting engine ... Inside the Start() func, started starts off false Inside the Start() func, then turns to true you'd think it would be started here ... {0 true} Engine started? false
Calling functions on the structs makes sense but I wonder if I'm trying to manipulate internal state in the wrong way? Or maybe I don't understand scopes. If someone could help me through this, I'd value it greatly for reference.
Also if someone has a preferred or idiomatic method for initializers. For example, the engine might default to 4 cylinders.
Methods
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. This is because pointer methods can modify the receiver; invoking them on a copy of the value would cause those modifications to be discarded.
Therefore, for your Engine
Start
method to work, use a pointer receiver, since the method modifies the receiver. For example,
package main
import (
"fmt"
)
type Engine struct {
cylinders int
started bool
}
func (engine *Engine) Start() {
fmt.Println("Inside the Start() func, started starts off", engine.started)
engine.started = true
fmt.Println("Inside the Start() func, then turns to", engine.started)
// this is a sanity check
}
func (engine *Engine) IsStarted() bool {
return engine.started
}
func main() {
var engine Engine
fmt.Println(engine.IsStarted())
engine.Start()
fmt.Println(engine.IsStarted())
}
Output:
false
Inside the Start() func, started starts off false
Inside the Start() func, then turns to true
true
You're passing the receiver by value. Pass by pointer instead:
func (engine *Engine) Start() {
^
}