๐Ÿ—๏ธ

Struct vs Class โ€” Living Without Inheritance

Go's Struct Embedding Instead of Ruby's class < Base

For Ruby developers, class is like air. Everything lives inside classes. class User < ApplicationRecord โ€” inherit functionality, mix in modules with include.

Go has no class and no inheritance. Structs define data structures, and methods attach to those structs. That's it.

Ruby's class vs Go's struct

Ruby: class User; attr_accessor :name, :email; end
Go: type User struct { Name string; Email string }

Looks similar but fundamentally different. Ruby's class is a vessel for instance variables, methods, inheritance, mixins. Go's struct is just a bundle of data fields. Methods attach from outside.

No Inheritance โ€” Embedding Instead

class Admin < User is impossible in Go. Instead, embed one struct inside another:

type Admin struct {
    User  // embedding โ€” brings all User fields and methods
    Role string
}

Admin didn't "inherit" User โ€” it "contains" User. admin.Name accesses User's field, and User's methods are callable directly.

Similar to Ruby's include UserModule. But Go's embedding happens at the type level with zero runtime overhead.

No new

Ruby: User.new(name: "kim") โ€” calls initialize. Go has no constructor. User{Name: "kim"} creates a literal, or by convention you write a NewUser("kim") factory function.

private/public by Case

Instead of Ruby's private, protected, public keywords, Go uses the first letter. Uppercase = exported (accessible from outside). Lowercase = package-internal. User.Name is accessible, User.name is not.

Ruby to Go

1

Ruby: class < Base inheritance โ†’ Go: struct embedding (composition)

2

Ruby: attr_accessor โ†’ Go: struct fields (uppercase=public, lowercase=private)

3

Ruby: User.new(initialize) โ†’ Go: no constructor, User{} literal or NewUser() factory

4

Ruby: include Module โ†’ Go: struct embedding (similar but resolved at compile time)

Pros

  • Embedding enables code reuse without inheritance complexity (diamond problem, etc.)
  • Access control via case alone โ€” no need for private/public keywords

Cons

  • No dynamic dispatch (method_missing, respond_to?) โ€” no metaprogramming
  • No constructor โ€” initialization validation must be done manually in factory functions

Use Cases

When redesigning Rails ActiveRecord model inheritance in Go When converting Ruby's module include pattern to Go's embedding