Hacker News new | ask | show | jobs
by weberc2 2436 days ago
It is exactly composition; the compiler just generates the methods to automatically delegate to the anonymous elements. It's just syntax sugar that probably creates more confusion than it alleviates. It's not inheritance of any kind because the outer struct cannot be passed into a function that takes the inner element type (e.g., if you have `type B int` and `type A struct {B}` and `func foo(B) {}`, you can't pass an instance of `A` into `foo()`--`foo(A{B: 0})` is a compiler error.

As it is just composition, it is strictly better than inheritance, but still probably unnecessarily confusing versus just writing out the delegation methods yourself.

1 comments

>It's not inheritance of any kind because the outer struct cannot be passed into a function that takes the inner element type

My mistake. I was mislead by the blog post.

From the article:

> The new type would be interchangeable with the existing client which would minimize the need for changes to existing code.

So with go exported structs it is not fair to say they can be used interchangeably if at any point that instance is used as a parameter, field, or variable that defines the type?

In the examples given in the article, the thing that is used as parameters is the interface Client not the concrete type HTTPClient. Would that not allow CachedHTTPClient to passed around as if it was a Client and would that not show the same issues as inheritance?

Both types implement the `Client` interface. So either type can be passed into a function that accepts the `Client` interface, but a `CachedHTTPClient` cannot be passed into a function that accepts an `HTTPClient` (you would have to pass the `CachedHTTPClient.HTTPClient`) and of course not vice versa either. You can do the same thing in Java (forgive my syntax):

    public interface Client {
        ArrayList<String> getUsers();
        void createUser(String name);
    }

    public class HTTPClient implements Client {
        public ArrayList<String> getUsers() { /* ... */ }
        public void createUser(String name) { /* ... */ }
    }

    public class CachedHTTPClient implements Client {
        private HTTPClient httpClient;

        public ArrayList<String> getUsers() { /* ... */ }

        // In Go via "struct embedding", this method would be generated
        // automatically; in Java, we have to write it out. NBD.
        public void createUser(String name) { this.httpClient.createUser(name); }
    }
Makes sense. I guess I just don't see how exported types save you from any of the pitfalls of inheritance. In my mind composition is the use of existing types without being forced into a type contract.
Mind you, I don’t advocate for struct embedding, but it doesn’t force you into a type contract. The outer struct retains its type—it is not a subtype of the nested type so it can’t be used in places that take the inner type.

Literally all it does is automatically create methods on the outer struct that delegate to the anonymous member.

Unlike inheritance, there is no fragile base class problem and methods on the inner anonymous member can’t dispatch to methods on the outer struct. Also, the “parent” member is just another field in your struct. You can modify it or replace it at runtime, unlike the parent in OOP languages.