A C++ developer looks at Go (the programming language), Part 2: Modularity and Object Orientation

This is the second part of a series. Here are all 3 parts:

In part 1, I looked at the simpler features of Go, such as its general syntax, its basic type system, and its for loop. Here I mention its support for packages and object orientation. As before, I strongly suggest that you read the book to learn about this stuff properly. Again, I welcome any friendly corrections and clarifications.

Overall, I find Go’s syntax for object orientation a bit messy, inconsistent and frequently too implicit, and for most uses I prefer the obviousness of C++’s inheritance hierarchy. But after writing the explanations down, I think I understand it.

I’m purposefully trying not to mention the build system, distribution, or configuration, for now.

Packages

Go code is organized in packages, which are much like Java package names, and a bit like C++ namespaces. The package name is declared at the start of each file:

package foo

And files in other packages should import them like so:

package bar

import (
  "foo"
  "moo"
)

func somefunc() {
  foo.Yadda()
  var a moo.Thing
  ...
}

The package name should match the file’s directory name. This is how the import statements find the packages’ files. You can have multiple files in the same directory that are all part of the same package.

Your “main” package, with your main function, is an exception to this rule. Its directory doesn’t need to be named “main” because you won’t be importing the main package from anywhere.

Go doesn’t seem to allow nested packages, unlike C++ (foo::thing::Yadda) and Java (foo.thing.Yadda), though people seem to work around this by creating separate libraries, which seems awkward.

I don’t know how people should specify the API of a Go package without providing the implementation. C and C++ have header files for this purpose, separating declaration and implementation.

Structs

You can declare structs in go much as in C. For instance:

type Thing struct {
  // Member fields.
  // Notice the lack of the var keyword.
  a int
  B int // See below about symbol visibility
}

var foo Thing
foo.B = 3

var bar Thing = Thing{3}

var goo *Thing = new(Thing)
goo.B = 5

As usual, I have used the var form to demonstrate the actual type, but you would probably want to use the short := form.

Notice that we can create it as a value or as a pointer (using the built-in new() function), though in Go, unlike C or C++, this does not determine whether its actual memory will be on the stack or the heap. The compiler decides, generally based on whether the memory needs to outlive the function call.

Previously we’ve seen the built-in make() function used to instantiate slices and maps (and we’ll see it in part 3 with channels). make() is only for those built-in types. For our own types, we can use the new() function. I find the distinction a bit messy, but I generally dislike the whole distinction between built-in types and types that can be implemented using the language itself. I like how the C++ standard library is implemented in C++, with very little special support from the language itself when something is added to the library.

Go types often have “constructor” functions (not methods) which you should call to properly instantiate the type, but I don’t think there is any way to enforce correct initialization like a default constructor in C++ or Java. For instance:


type Thing struct {
  a int
  name string
  ...
}

func NewThing() *Thing {
  // 100 is a suitable default value for a in this type:
  f := Thing{100, nil}
  return &f
}

// Notice that different "constructors" must have different names,
// because go doesn't have function or method overloading.
func NewThingWithName(name string) *Thing {
  f := Thing{100, name}
  return &f
}

Embedding Structs

You can anonymously “embed” one struct within an other, like so:

type Person struct {
   Name string
}

type Employee struct {
  Person
  Position string
}

var a Employee
a.Name = "bob"
a.Position = "builder"

This feels a bit like inheritance in C++ and Java, but this is just containment. It doesn’t give us any real “is a” meaning and doesn’t give us real polymorphism. For instance, you can do this:

var e = new(Employee)

// Compilation error.
var p *Person = e

// This works instead.
// So if we thought of this as a cast (we probably shouldn't),
// this would mean that we have to explicitly cast to the base class.
var p *Person = e.Person

// This works.
e.methodOnPerson()

// And this works.
// Name is a field in the contained Person struct.
e.Name = 2

// These work too, but the extra qualification is unnecessary.
e.Person.methodOnPerson()

Interfaces, which we’ll see later, do give us some sense of an “is a” meaning.

Methods

Unlike C, but like C++ and Java classes, structs in Go can have methods – functions associated with the struct. But the syntax is a little different than in C++ or Java. Methods are declared outside of the struct declaration, and the association is made by specifying a “receiver” before the function name. For instance, this declares (and implements) a DoSomething method for the Thing struct:

func (t Thing) DoSomething() {
  ...
}

Notice that you have to specify a name for the receiver – there is no built-in “self” or “this” instance name. This feels like an unnecessary invitation to inconsistency.

You can use a pointer type instead, and you’ll have to if you want to change anything about the struct instance:

func (t *Thing) ChangeSomething() {
  t.a = 4
}

Because you should also want to keep your code consistent, you’d therefore want to use a pointer type for all method receivers. So I don’t know why the language lets it ever be a struct value type.

Unlike C++ or Java, this lets you check the instance for nil (Go’s null or nullptr), making it acceptable to call your method on a null instance. This reminds me of how Objective-C happily lets you call a method (“send a message to” in Objective-C terminology) on a nil instance, with no crash, even returning a nil or zero return value. I find that undisciplined in Objective-C, and it bothers me that Go allows this sometimes, but not consistently.

Unlike C++ or Java, you can even associate methods with non struct (non class) types. For instance:

type Meters int
type Feet int

func (Meters) convertToFeet() (Feet) {
  ...
}

Meters m = 10
f := p.convertToFeet()

No equality or comparison operator overloading

In C++, you can overload operator =, !=, <, >, etc, so you can use instances of your type with the regular operators, making your code look tidy:

MyType a = getSomething();
MyType b = getSomethingElse();
if (a == b) {
  ...
}

You can’t do that in Go (or Java, though it has the awkward Comparable interface and equals() method). Only some built-in types are comparable in Go – the numeric types, string, pointers, or channels, or structs or arrays made up of these types. This is an issue when dealing with interfaces, which we’ll see later.

Symbol Visibility: Uppercase or lowercase first letter

Symbols (types, functions, variables) that start with an uppercase letter are available from outside the package. Struct methods and member variables that start with an uppercase letter are available from outside the struct. Otherwise they are private to the package or struct.

For instance:

type Thing int // This type will be available outside of the package.
var Thingleton Thing// This variable will be available outside of the package.

type thing int // Not available outside of the package.
var thing1 thing // Not available outside of the package.
var thing2 Thing // Not available outside of the package.

// Available outside of the package.
func DoThing() {
  ...
}

// Not available outside of the package.
func doThing() {
  ...
}

type Stuff struct {
  Thing1 Thing // Available outside of the package.
  thing2 Thing // "private" to the struct.
}

// Available outside of the struct.
func (s Stuff) Foo() {
  ...
}

// Not available outside of the struct.
func (s Stuff) bar() {
  ...
}

// Not available outside of the package.
type localstuff struct {
...
}

I find this a bit strange. I prefer the explicit public and private keywords in C++ and Java.

Interfaces

Interfaces have methods

If two Go types satisfy an interface then they both have the methods of that interface. This is similar to Java interfaces. A Go interface is also a bit like a completely abstract class in C++ (having only pure virtual methods), but it’s also a lot like a C++ concept (not yet in C++, as of C++17). For instance:

type Shape interface {
  // The interface's methods.
  // Note the lack of the func keyword.
  SetPosition(x int, y int)
  GetPosition() (x int, y int)
  DrawOnSurface(s Surface)
}

type Rectangle struct {
  ...
}

// Methods to satisfy the Shape interface.
func (r *Rectangle) SetPosition(x int, y int) {
  ...
}

func (r *Rectangle) GetPosition() (x int, y int) {
  ...
}
func (r *Rectangle) DrawOnSurface(s Surface) {
   ...
}

// Other methods:
func (r *Rectangle) setCornerType(c CornerType) {
   ...
}
func (r *Rectangle) cornerType() (CornerType) {
   ...
}

type Circle struct {
  ...
}

// Methods to satisfy the Shape interface.
func (c *Circle) SetPosition(x int, y int) {
  ...
}

func (c *Circle) GetPosition() (x int, y int) {
  ...
}

func (c *Circle) DrawOnSurface(s Surface) {
  ...
}

// Other methods:
...

You can then use the interface type instead of the specific “concrete” type:

var someCircle *Circle = new(Circle)
var s Shape = someCircle
s.DrawOnSurface(someSurface)

Notice that we use a Shape, not a *Shape (pointer to Shape), even though we are casting from a *Circle (pointer to circle). “Interface values” seem to be implicitly pointer-like, which seems unnecessarily confusing. I guess it would feel more consistent if pointers to interfaces just had the same behaviour as these “interface values”, even if the language had to disallow interface types that weren’t pointers.

Types satisfy interfaces implicitly

However, there is no explicit declaration that a type should implement an interface.

In this way Go interfaces are like C++ concepts, though C++ concepts are instead a purely compile-time feature for use with generic (template) code. Your class can conform to a C++ concept without you declaring that it does. And therefore, like Go interfaces, you can, if you must, use an existing type without changing it.

The compiler still checks that types are compatible, but presumably by checking the types’ list of methods rather than checking a class hierarchy or list of implemented interfaces. For instance:

var a *Circle = new(Circle)
var b Shape = a // OK. The compiler can check that Circle has Shape's methods.

Like C++ with dynamic_cast, Go can also check at runtime. For instance, you can check if one interface value refers to an instance that also satisfies another interface:

// Sometimes the Shape (our interface type) is also a Drawable
// (another interface type), sometimes not.
var a Shape = Something.GetShape()

// Notice that we want to cast to a Drawable, not a *Drawable,
// because Drawable is an interface.
var b = a.(Drawable) // Panic (crash) if this fails.

var b, ok = a.(Drawable) // No panic.
if ok {
  b.DrawOnSurface(someSurface)
}

Or we can check that an interface value refers to a particular concrete type. For instance:

// Get Shape() returns an interface value.
// Shape is our interface.
var a Shape = Something.GetShape()

// Notice that we want to cast to a *Thing, not a Thing,
// because Thing is a concrete type, not an interface.
var b = a.(*Thing) // Panic (crash) if this fails.

var b, ok = a.(*Thing) // No panic.
if ok {
  b.DoSomething()
}

Runtime dispatch

Interface methods are also like C++ virtual methods (or any Java method), and interface variables are also like instances of polymorphic base classes. To actually call the interface’s method via an interface variable, the program needs to examine its actual type at runtime and call that type’s specific method. Maybe, as with C++, the compiler can sometimes optimize away that indirection.

This is obviously not as efficient as directly calling a method, identified at compile time, of a templated type in a C++ template. But it is obviously much simpler.

Comparing interfaces

Interface values can be compared sometimes, but this seems like a risky business. Interface values are:

  • Not equal if their types are different.
  • Not equal if their types are the same and only one is nil.
  • Equal if their types are the same, and the types are comparable (see above), and their values are equal.

But if the types are the same, yet those types are not comparable, Go will cause a “panic” at runtime.

Wishing for an implements keyword

In C++ you can, if you wish, explicitly declare that a class should conform to the concept, or you can explicitly derive from a base class, and in Java you must use the “implements” keyword. Not having this with Go would take some getting used to. I’d want these declarations to document my architecture, explicitly showing what’s expected of my”concrete” classes in terms of their general purpose instead of just expressing their that by how some other code happens to use them. Not having this feels fragile.

The book suggests putting this awkward code somewhere to check that a type really implements an interface. Note the use of _ to mean that we don’t need to keep a named variable for the result.

var _ MyInterface = (*MyType)(nil)

The compiler should complain that the conversion is impossible if the type does not satisfy the interface. I think it would be wise this as the very minimum of testing, particularly if your package is providing types that are not really used in the package itself. For me, this is a poor substitute for an obvious compile-time check, using a specific language construct, on the type itself.

Interface embedding

Embedding an interface in an interface

Go has no notion of inheritance hierarchies, but you can “embed” one interface in another, to indicate that a class that satisfies one interface also satisfies the other. For instance:

type Positionable interface {
  SetPosition(x int, y int)
  GetPosition() (x int, y int)
}

type Drawable interface {
  drawOnSurface(s Surface) }
}

type Shape interface {
  Positionable
  Drawable
}

To satisfy the Shape interface, any type must also satisfy the Drawable and Positionable interfaces. Therefore, any type that satisfies the Shape interface can be used with a method associated with the Drawable or Positionable interfaces. So it’s a bit like a Java interface extending another interface.

Embedding an interface-satisfying struct in a struct

We saw earlier how you can embed one struct in another anonymously. If the contained struct implements an interface, then the containing struct then also implements that interface, with no need for manually-implemented forwarding methods. For instance:

type Drawable interface {
 drawOnSurface(s Surface)
}

type Painter struct {
  ...
}

// Make Painter satisfy the Drawable interface.
func (p *Painter) drawOnSurface(s Surface) {
  ...
}

type Circle struct {
 // Make Circle satisfy the Drawable interface via Painter.
 Painter
 ...
}

func main() {
  ...
  var c *Circle = new(Circle)
 
  // This is OK.
  // Circle satisfies Drawable, via Painter
  c.drawOnSurface(someSurface)

  // This is also OK.
  // Circle can be used as an interface value of type Drawable, via Painter.
  var d Drawable = c
  d.drawOnSurface(someSurface)
}

This again feels a bit like inheritance.

I actually quite like how the (interfaces of the) anonymously contained structs affect the interface of the parent struct, even with Go’s curious interface system, though I wish the syntax was more obvious about what is happening. It might be nice to have something similar in C++. Encapsulation instead of inheritance (and the Decorator pattern) is a perfectly valid technique, and C++ generally tries to let you do things in multiple ways without having an opinion about what’s best, though that can itself be a source of complexity. But in C++ (and Java), you currently have to hand-code lots of forwarding methods to achieve this and you still need to inherit from something to tell the type system that you support the encapsulated type’s interface.

 

20 thoughts on “A C++ developer looks at Go (the programming language), Part 2: Modularity and Object Orientation

  1. Go does support nested packages. You can get composed struct types out of their combined struct, you just didn’t use the correct syntax (var x StructA = y.StructA). You can implicitly call functions of composed structs, you do it later in your article after you say you can’t.

    1. > Go does support nested packages.

      OK. Thanks. I must have misunderstood something I read somewhere.

      > You can get composed struct types out of their combined struct, you just didn’t use the correct syntax (var x StructA = y.StructA).

      Yes, but this is just accessing the encapsulated (contained) child struct. It’s not like saying that the parent struct “is a” instance of the child struct. Then again, your point below about the methods shows that there is some sense of “is a”. Thanks.

      > You can implicitly call functions of composed structs, you do it later in your article after you say you can’t.

      That later text is about calling interface methods. But yes, it does indeed seem to work with just regular structs. Thanks. I was sure I had tried that.

  2. One thing to note is that the decision about whether to use a plain or pointer receiver does make a difference when you’re using interfaces. Consider the following type:

    type MyType int
    func (v MyType) Foo() {}
    func (p *MyType) Bar() {}
    var a MyType
    var b *MyType

    Here, the variables “a” and “b” have different method sets: “a” only has a single method “Foo”, while “b” also has a method “Bar”. The call “a.Bar()” still works because the compiler converts it to “(&a).Bar()”.

    Now lets introduce an interface:

    type Iface interface {
    Bar()
    }

    Only “b” can be assigned to this interface type. The reason for this is that the only operations you can perform on an interface variable’s dynamic value are to copy it or set it to something else. You can’t create a pointer to it, so there is no way you could invoke the “Bar” method on a plain “MyType” value.

  3. Minor correction:

    type Stuff struct {
    Thing1 Thing // Available outside of the package.
    thing2 Thing // “private” to the struct.
    }

    Like variables, struct field type definition comes after the name. I’m sure this was just a product of habit ;}

  4. Thank you for writing this. It’s a good strating point for discussion, and one that the community should embrace. That being said, it seems as if you haven’t understood the “why” of the language. Obviously C++ is way more rich as a langauge. You can do just about everything in just about any way imaginable. But with that immense freedom comes complexity. It’s the the way you solve the problem that becomes important, instead of solving the problem. Rob Pike wrote an excelent article on the subject which I invite you to read. Please let me know what you think of it. https://commandcenter.blogspot.fr/2012/06/less-is-exponentially-more.html
    EDIT: 20 min video on the subject. https://www.youtube.com/watch?v=rFejpH_tAHM

    1. > It’s a good strating point for discussion, and one that the community should embrace.

      I’m sure the community has better things to do. My understanding of Go is not deep enough to be that useful.

      > it seems as if you haven’t understood the “why” of the language

      Thanks, but I think I have understood it, and mentioned much of it in the text, and tried to discuss it thoughtfully. Just because I don’t like all the choices the language has made, or don’t think the language is the best choice for all my projects, that doesn’t mean that I don’t understand those choices or why other people would like them. These are choices, not rights or wrongs, so people are not logically wrong if they don’t just wholeheartedly convert to the same belief system.

      The few things with which I’ve expressed disappointment are things that, if corrected, would seem to make Go simpler and would make Go code more readable, furthering its goals. For instance, lack of generics (which I feel sure will arrive one day) to avoid the confusing built-in/not-built-in dichotomy, slices and maps that acts like reference types, interface values that act like pointers but don’t use pointer syntax (the equivalents do in C++), and not being able to declare that a type must satisfy an interface. Basically anything that is surprising and needs to be documented. But I am in no way saying that I am an expert and everybody should change the language however I like right now. Because I don’t have jam for brains.

      I am, after all, an enthusiastic C++ developer. I am comfortable with C++. I feel I can express myself in C++. I like that people can use complicated parts of the language, such as weird metaprogramming, to implement APIs that feel simple and require only an understanding of the core language. But I also know that learning C++ is almost a lifelong project, if you want to know it completely. Go, on the other hand, will now be one of my candidates for a first programming language when I know someone starting to learn programming.

      Thanks for the link to the video. It’s a perfect summary of the language’s goals. It didn’t tell me much that was new after the book but I wish we could all have Rob Pike’s calm confidence.

      Sorry if that all sounds ranty. It’s not mean to. I know you mean well and I am thankful for your response.

  5. A header file isn’t a great way to document an API for a human; it’s a great way to document it for a compiler. Go doesn’t need APIs documented for its compiler. If you want to document an API for a human, use godoc to generate documentation for human consumption.

    I liked part 1, but much of part 2 seems to start from the mistaken viewpoint that Go is an object-oriented language, which it isn’t. Structs don’t support inheritance, and its only polymorphic feature is interfaces. Embedding should not be confused for inheritance.

    The article also misses the advantages of implicit interface implementation (though it would be nice if it had an *optional* explicit implementation, just to verify correctness at compile time, that wasn’t so kludgy as the “var _ =” method noted in the article). Because interfaces are implemented implicitly, you can define an interface where it’s consumed rather than where it’s implemented. You can abstract away something in the standard library or a third-party library. You never have to write interfaces defensively because one day someone *might* want to write another implementation. You can just write implementations, and when something needs an abstraction, it can create one. It’s a subtle thing that takes some time and experience with the language to fully get a feel for.

    1. Thanks, but I think you are countering points that I don’t think I have made.

      > A header file isn’t a great way to document an API for a human; it’s a great way to document it for a compiler

      Yes, that’s what I would like to know how to do – How do you provide a package without providing the source code. Surely there is some interface declaration/definition that allows application code to use the API provided by that package.

      I don’t think I suggested, or didn’t mean to suggest, that C++ headers are a great way to document an API. Update: Reading my text again, I guess I could have been clearer in my use of the word “specify”. Sorry. I didn’t mean specify it to a human.

      > part 2 seems to start from the mistaken viewpoint that Go is an object-oriented language, which it isn’t.

      It does supports object orientation, but via containment and (implicit) interfaces. It is not a purely object-orientated language, but neither is C++. Maybe Java is. I don’t mean to confuse containment/embedding with inheritance. I haven’t said it _is_ inheritance. I’ve said it’s a bit like inheritance – that there is a sense of an “is a” relationship, which I believe is intended by the containment/embedding kind of O-O. I just wanted to show how they are similar, and how they are different, to what a C++ developer might expect. That seems valid.

      > Because interfaces are implemented implicitly, you can define an interface where it’s consumed rather than where it’s implemented

      Yes, I understand that. It’s mentioned in the book, and I intended to mention it too. However, I don’t find it particularly compelling and it doesn’t feel like the common case. I’d need more examples of it being useful in real world – there must be a few.

  6. > Notice that you have to specify a name for the receiver – there is no built-in “self” or “this” instance name. This feels like an unnecessary invitation to inconsistency.

    I would argue that using `this`, `self` would is more inconsistent :). For example:

    func (rect *Rect) Size() (int, int)
    func (rend *Renderer) Render(rect *Rect)

    func SizeOf(rect *Rect) (int, int)
    func RenderRect(rend *Renderer, rect *Rect)

    // vs.

    func (this *Rect) Size() (int, int)
    func (this *Renderer) Render(rect *Rect)

    Notice how the meaning of “this” changes inside different methods.

    With regards to receiver naming inconsistency, there are many tools to check for such errors. That particular mistake is detected by golint.

    > So I don’t know why the language lets it ever be a struct value type.

    For primitive types it’s usually clearer to use a non-pointer type.

    type Fixed100 int64
    func (a Fixed100) Add(b Fixed100) Fixed100 { return a + b }

    And, declaring a method on a pointer to a func type just feels weird:

    type HandlerFunc func(w ResponseWriter, r *Request)

    func (fn HandlerFunc) ServeHttp(w ResponseWriter, r *Request) { fn(w, r) }
    // vs.
    func (fn *HandlerFunc) ServeHttp(w ResponseWriter, r *Request) { (*fn)(w, r) }

    > “Interface values” seem to be implicitly pointer-like, which seems unnecessarily confusing.

    Interface values act like structs that contain any value with some extra checks. Also, having non-pointer interface implementers isn’t uncommon. As an example take a look at http.HandlerFunc.

    However, I do agree that the part where you assign a pointer type to a non-pointer interface{} is confusing, unless you understand the internals of interface. (https://research.swtch.com/interfaces)

    circle := &Circle{5}
    var shape Shape
    shape = circle

    Mentally thinking it as a struct, makes it more intuitive:

    circle := &Circle{5}
    var shape Shape
    shape.type = typeof(circle)
    shape.data = circle

    Where “type” must have the method-set of Shape.

    > How do you provide a package without providing the source code.

    https://github.com/golang/proposal/blob/master/design/2775-binary-only-packages.md
    https://github.com/tcnksm/go-binary-only-package

    Of course, this will get you all the problems that you get when you don’t have the source around.

    1. Thanks.

      > Notice how the meaning of “this” changes inside different methods.

      The type of “this” changes, but the meaning (the particular meaning that I think is interesting) remains the same. It doesn’t seem to cause confusion in C++ or java – Making it possible to rename the “this” variable is not a complication that even C++ developers would welcome or see much need for.

      > For primitive types it’s usually clearer to use a non-pointer type.

      Yes, that’s why I mentioned the struct types. However, I wonder if it would make much difference if you always used pointers even for simple types. Basically, I feel that the language shouldn’t be giving me this choice to make, and shouldn’t be asking me to repeat that choice for every single method of a type.

      1. That’s a bad comparison to Java, java restricts you on a per-file basis to one class, so it’s rare you’ll run into a file where this means multiple things.

        I like having the choice to use a non-pointer receiver because there are times when I want to have a method return a modified receiver without it modifying the calling struct.

  7. > It doesn’t seem to cause confusion in C++ or java – Making it possible to rename the “this” variable is not a complication that even C++ developers would welcome or see much need for.

    I agree with it… at least most of the time. There are two places where I consider “this” to be more annoying:

    1. Refactoring

    func (this *Surface) RenderRect(rect *Rect) {
    w := rect.Max.X – rect.Min.X – this.Margin
    h := rect.Max.Y – rect.Min.Y – this.Margin
    // … use the variables some how
    }

    // into

    func (this *Surface) RenderRect(rect *Rect) {
    w, h := rect.Size(this.Margin)
    // … use the variables some how
    }

    func (this Rect) Size(margin int) (int, int) {
    w := this.Max.X – this.Min.X – margin
    h := this.Max.Y – this.Min.Y – margin
    return w, h
    }

    Notice, how the “this” needs to be added/removed in multiple places.

    2. The other place would be where you have code that jumps a lot between different types. A facilitated example:

    func (chat *Chat) Login(user *User) {
    if err := chat.Authenticate(user); err != nil {
    user.LoginFailed(chat, err)
    return
    }

    chat.Include(user)
    user.Send(chat.RecentHistory())
    user.Notify(“Welcome back ” + chat.Name)

    }

    func (chat *Chat) Authenticate(user *User) error { … }
    func (user *User) LoginFailed(chat *Chat, err error) {
    user.loginError = err
    user.Send(“Login failed to ” + chat.Name)
    user.Disconnect()
    }

    Notice how the “this” would swap when you read the code from top to bottom (or debug it). The better you are with context switches the less it will confuse you.

    /Of course, there are other solutions to that problem (e.g. DCI Contexts)./

    3. With regards to non-annoying cases, seeing “client” instead of “this” doesn’t really bother that much. It’s something you get used to, in the same way you got used to “this” and “self” in the first place.

    > Basically, I feel that the language shouldn’t be giving me this choice to make, and shouldn’t be asking me to repeat that choice for every single method of a type.

    Let’s take this example in C (hopefully I did not make any mistakes :)):

    struct Vector { int x, y; };
    struct Rect { Vector min, max; };

    void render_a(struct Rect rect) { }
    void render_b(struct Rect *rect) { }

    Should we require that rect must always be a pointer?

    Technically, speaking it doesn’t make a difference, and sufficiently smart compiler would be able to optimize the pointer version similarly. However, it does carry some semantic information:

    type Vector struct { X, Y int }

    func (a *Vector) Add(b Vector) { … }
    vs.
    func (a Vector) Add(b Vector) Vector { … }

    /There are of course other ways to communicate this difference/

  8. > Let’s take this example in C

    Object-orientation will always be awkward in C, requiring people to follow specific guidelines.

    In C++, the this variable, which doesn’t exist explicitly in C, is always a pointer. It helps that C++ has the const keyword too. I could imagine that it would still always be a pointer if C++ started letting you define methods on non-class types. (For which it would need something like the type keyword.) That would be less than optimal, I guess, so maybe the compiler would complain if you used the wrong form with the wrong kind of type. Or this would probably persuade even more C++ people that “this” should actually be a reference. (I’ve heard that suggested before).

    Thanks for the discussion.

    1. > Object-orientation will always be awkward in C, requiring people to follow specific guidelines.

      I probably should have made the example bit more complicated; to show what I meant :)… Now in C++

      struct Vector { int x, y; };
      struct Rect { Vector min, max; };

      struct Surface { // could be a class as well
      Vector offset;
      int render_a(struct Rect rect) { return this->offset.x + rect.min.x; }
      int render_b(struct Rect *rect) { return this->offset.x + rect->min.x; }
      };

      Should we restrict “rect” to be a pointer here? /Or whatever the current recommended pointer template is./

      I see receiver as an argument to the function and the type&variable as a namespace for functions. This also makes somewhat clearer why “nil” receivers work in Go.

Leave a Reply

Your email address will not be published. Required fields are marked *