Problem overview
A rectangle has two numbers, a width and a height, and you can ask it questions about them. This lesson asks for a Rectangle that can answer three: its area, its perimeter, and whether it is a square.
Go has no classes. The same idea is a struct, which bundles named fields, plus methods, which are functions attached to the struct. A constructor is an ordinary function, by convention named NewRectangle, that returns a filled-in struct.
For NewRectangle(3, 4) the area is 12, the perimeter is 14 and isSquare() is false. For NewRectangle(5, 5) they are 25, 20 and true. Each rectangle keeps its own sizes, so two values never interfere with each other.
Approach
Nothing needs to be computed ahead of time. The struct stores only the two sizes, and every method works its answer out from them when it is called:
- the area is
width * height; - the perimeter walks around all four sides,
width + height + width + height, which is2 * (width + height); - the rectangle is a square exactly when the two sizes are equal.
Each method is a single expression, so each runs in constant time.
Step by step
- Declare the fields. Inside the struct,
widthandheight, bothint. EveryRectanglevalue carries its own copy of them. - Write the constructor.
NewRectanglereturns a struct literal. Inwidth: width, the name on the left is the field and the one on the right is the parameter. - Write
area. Multiply the two fields. The receiver, namedthishere, is the rectangle the method was called on. - Write
perimeter. Add the two sizes first, using parentheses, then double the sum. - Write
isSquare. A comparison already produces abool, so returnthis.width == this.heightdirectly. Noifis needed.
The solution in Go
type Rectangle struct {
width int
height int
}
func NewRectangle(width int, height int) Rectangle {
return Rectangle{width: width, height: height}
}
func (this Rectangle) area() int {
return this.width * this.height
}
func (this Rectangle) perimeter() int {
return 2 * (this.width + this.height)
}
func (this Rectangle) isSquare() bool {
return this.width == this.height
}
Go details worth knowing:
- The receiver.
(this Rectangle)before the method name is what makes a function a method. The name is your choice. Many Go programmers write a short one such asr, but the lesson usesthis, which reads like other languages. - Value receivers. These three methods only read the fields, so a value receiver is enough: the method gets a copy of the rectangle, and that is fine because nothing changes.
- Lowercase names.
widthandheightstart with a lowercase letter, so code outside the package cannot see them. Methods and the constructor inside the package can. - The zero value. A
Rectangledeclared withvar z Rectanglehas both fields 0, so its area is 0 andisSquare()istrue. The constructor is how you give a rectangle real sizes.
Why store the sizes and not the answers
The struct could also store the area and the perimeter in extra fields, filled in by the constructor. That would be a mistake for a beginner's class: three numbers that must always agree are three chances to get out of step. With only the width and the height stored, the other answers are always correct, because they are recomputed from the sizes each time they are asked for. Storing the smallest set of facts and deriving everything else is a good habit for any struct.
A method or a plain function
Go lets you write the same thing as a plain function, func area(r Rectangle) int, and call it as area(r). A method does the same job with a different call, r.area(). The method form keeps the struct and the questions you can ask it together, and it is the form the lesson's tests use.
Using it
Create values with the constructor and call the methods with a dot:
for _, r := range []Rectangle{NewRectangle(3, 4), NewRectangle(5, 5), NewRectangle(2, 10), NewRectangle(10, 2)} {
fmt.Println(r.width, r.height, "->", r.area(), r.perimeter(), r.isSquare())
}
This prints:
3 4 -> 12 14 false
5 5 -> 25 20 true
2 10 -> 20 24 false
10 2 -> 20 24 false
The last two lines show that turning a rectangle on its side changes neither the area nor the perimeter. The lesson's tests print the boolean as True or False; that is the tests' output format, while Go itself spells it true and false.
Structs are copied on assignment. After a := NewRectangle(3, 4) and b := a, setting b.width = 9 leaves a alone: a.area() is 12 and b.area() is 36.
Edge cases and common mistakes
- Forgetting the parentheses in the perimeter.
2 * this.width + this.heightdoubles only the width. For a 3 by 4 rectangle it gives 10 instead of 14. - Using
=instead of==. InisSquare, a single=is an assignment and does not compile as a condition. - Swapping field and parameter in the literal.
Rectangle{width: height, height: width}compiles but builds a turned rectangle. Area and perimeter don't notice, but the stored sizes are wrong. - Treating a square as a special case. A square is just a rectangle whose sides are equal, so
NewRectangle(5, 5)needs no separate type.
