[Go] user-defined type
개요
사용자 정의 타입은 Go에서 새로운 타입을 생성하는 방법입니다.
주요 특징:
- 타입 별칭 vs 새 타입: type 키워드로 둘 다 가능
- 구조체: 여러 필드를 묶은 복합 타입
- 메서드: 타입에 연결된 함수
- 임베딩: 상속 대신 조합으로 기능 확장
- 값 vs 포인터 리시버: 복사 vs 참조 선택
- 태그: 구조체 필드에 메타데이터 추가
- 제로값: 모든 타입은 의미 있는 제로값 보유
타입 정의
1. 새로운 타입 정의
package main
import "fmt"
// 기존 타입 기반 새 타입
type Celsius float64
type Fahrenheit float64
// 서로 다른 타입이므로 직접 할당 불가
func typeDefinition() {
var c Celsius = 25.0
var f Fahrenheit = 77.0
// ❌ 타입이 다르므로 컴파일 에러
// c = f
// ✅ 명시적 변환 필요
c = Celsius(f)
fmt.Printf("%.2f°C\n", c)
}
// 타입별 메서드 정의 가능
func (c Celsius) ToFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ToCelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
func main() {
c := Celsius(100)
f := c.ToFahrenheit()
fmt.Printf("%.2f°C = %.2f°F\n", c, f)
// 100.00°C = 212.00°F
}
2. 타입 별칭 (Type Alias)
// 타입 별칭 (= 사용)
type MyInt = int
func typeAlias() {
var a MyInt = 10
var b int = 20
// ✅ 같은 타입이므로 할당 가능
a = b
b = a
fmt.Println(a, b)
}
// 주요 차이점
type NewInt int // 새로운 타입
type AliasInt = int // int의 별칭
func difference() {
var n NewInt = 10
var a AliasInt = 20
var i int = 30
// NewInt와 int는 다른 타입
// n = i // 컴파일 에러
n = NewInt(i) // 변환 필요
// AliasInt와 int는 같은 타입
a = i // OK
i = a // OK
}
3. 기본 타입 기반 커스텀 타입
type UserID int64
type Email string
type Age uint8
type Status int
const (
StatusPending Status = iota
StatusApproved
StatusRejected
)
func (s Status) String() string {
switch s {
case StatusPending:
return "Pending"
case StatusApproved:
return "Approved"
case StatusRejected:
return "Rejected"
default:
return "Unknown"
}
}
func main() {
userID := UserID(12345)
email := Email("user@example.com")
age := Age(25)
status := StatusApproved
fmt.Printf("User: %d, %s, %d, %s\n", userID, email, age, status)
}
구조체 (Struct)
1. 기본 구조체
type Person struct {
Name string
Age int
Email string
}
func basicStruct() {
// 방법 1: 필드 이름 지정
p1 := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}
// 방법 2: 순서대로 초기화 (비권장)
p2 := Person{"Bob", 25, "bob@example.com"}
// 방법 3: 일부만 초기화 (나머지는 제로값)
p3 := Person{Name: "Charlie"}
fmt.Println(p3) // {Charlie 0 }
// 방법 4: var 선언 (모든 필드가 제로값)
var p4 Person
fmt.Println(p4) // { 0 }
// 방법 5: new 사용 (포인터 반환)
p5 := new(Person)
fmt.Printf("%T\n", p5) // *main.Person
fmt.Println(p1, p2)
}
2. 익명 구조체
func anonymousStruct() {
// 정의와 동시에 사용
person := struct {
Name string
Age int
}{
Name: "Alice",
Age: 30,
}
fmt.Println(person)
// 슬라이스로 사용
people := []struct {
Name string
Age int
}{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
for _, p := range people {
fmt.Printf("%s is %d years old\n", p.Name, p.Age)
}
}
// 테이블 드리븐 테스트에서 자주 사용
func TestSomething() {
tests := []struct {
name string
input int
expected int
}{
{"zero", 0, 0},
{"positive", 5, 25},
{"negative", -3, 9},
}
for _, tt := range tests {
// 테스트 로직
_ = tt
}
}
3. 중첩 구조체
type Address struct {
Street string
City string
Country string
}
type Employee struct {
Name string
Age int
Address Address // 중첩 구조체
}
func nestedStruct() {
emp := Employee{
Name: "Alice",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Seoul",
Country: "Korea",
},
}
fmt.Println(emp.Address.City) // Seoul
}
4. 구조체 임베딩 (Embedding)
type Animal struct {
Name string
Age int
}
func (a Animal) Speak() {
fmt.Printf("%s makes a sound\n", a.Name)
}
type Dog struct {
Animal // 임베딩 (익명 필드)
Breed string
}
func (d Dog) Bark() {
fmt.Printf("%s barks\n", d.Name)
}
func embedding() {
dog := Dog{
Animal: Animal{Name: "Buddy", Age: 3},
Breed: "Golden Retriever",
}
// 임베딩된 필드에 직접 접근
fmt.Println(dog.Name) // Buddy (dog.Animal.Name과 동일)
// 임베딩된 메서드 호출
dog.Speak() // Buddy makes a sound
dog.Bark() // Buddy barks
}
5. 다중 임베딩
type Walker interface {
Walk()
}
type Swimmer interface {
Swim()
}
type WalkingAnimal struct {
Name string
}
func (w WalkingAnimal) Walk() {
fmt.Printf("%s is walking\n", w.Name)
}
type SwimmingAnimal struct {
Name string
}
func (s SwimmingAnimal) Swim() {
fmt.Printf("%s is swimming\n", s.Name)
}
type Duck struct {
WalkingAnimal
SwimmingAnimal
}
func multipleEmbedding() {
duck := Duck{
WalkingAnimal: WalkingAnimal{Name: "Donald"},
SwimmingAnimal: SwimmingAnimal{Name: "Donald"},
}
duck.Walk() // Donald is walking
duck.Swim() // Donald is swimming
// ⚠️ 필드 이름 충돌 시 명시적 접근 필요
// duck.Name // 애매모호하므로 컴파일 에러
fmt.Println(duck.WalkingAnimal.Name)
fmt.Println(duck.SwimmingAnimal.Name)
}
구조체 태그 (Struct Tags)
import (
"encoding/json"
"fmt"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
Password string `json:"-"` // JSON에 포함 안 함
Age int `json:"age,omitempty"`
}
func structTags() {
user := User{
ID: 1,
Name: "Alice",
Email: "alice@example.com",
Password: "secret123",
Age: 30,
}
// JSON 마샬링
jsonData, _ := json.Marshal(user)
fmt.Println(string(jsonData))
// {"id":1,"name":"Alice","email":"alice@example.com","age":30}
// JSON 언마샬링
jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com"}`
var user2 User
json.Unmarshal([]byte(jsonStr), &user2)
fmt.Printf("%+v\n", user2)
}
태그 활용 예제
import "reflect"
type Product struct {
Name string `json:"name" xml:"name" db:"product_name" validate:"required"`
Price float64 `json:"price" xml:"price" db:"price" validate:"min=0"`
}
func readTags() {
p := Product{}
t := reflect.TypeOf(p)
field, _ := t.FieldByName("Name")
fmt.Println("JSON tag:", field.Tag.Get("json")) // name
fmt.Println("DB tag:", field.Tag.Get("db")) // product_name
fmt.Println("Validate:", field.Tag.Get("validate")) // required
}
메서드 (Methods)
1. 값 리시버 (Value Receiver)
type Rectangle struct {
Width float64
Height float64
}
// 값 리시버 - 복사본으로 전달
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 값을 변경해도 원본은 안 바뀜
func (r Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
func valueReceiver() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("Area:", rect.Area()) // 50
rect.Scale(2)
fmt.Println("After scale:", rect.Width) // 10 (변경 안됨!)
}
2. 포인터 리시버 (Pointer Receiver)
type Counter struct {
count int
}
// 포인터 리시버 - 원본을 수정
func (c *Counter) Increment() {
c.count++
}
func (c *Counter) Decrement() {
c.count--
}
func (c *Counter) Value() int {
return c.count
}
func pointerReceiver() {
counter := &Counter{}
counter.Increment()
counter.Increment()
counter.Decrement()
fmt.Println("Count:", counter.Value()) // 1
// 값으로 선언해도 Go가 자동으로 주소를 전달
counter2 := Counter{}
counter2.Increment() // (&counter2).Increment()와 동일
fmt.Println("Count2:", counter2.Value()) // 1
}
3. 리시버 선택 가이드
// ✅ 포인터 리시버를 사용해야 하는 경우:
type LargeStruct struct {
data [10000]int
}
// 1. 메서드가 리시버를 수정해야 함
func (l *LargeStruct) Update(index, value int) {
l.data[index] = value
}
// 2. 구조체가 큼 (복사 비용 절약)
func (l *LargeStruct) Process() {
// 큰 구조체 복사 피함
}
// 3. 일관성 (일부 메서드가 포인터 리시버면 모두 포인터)
func (l *LargeStruct) Read(index int) int {
return l.data[index]
}
// ✅ 값 리시버를 사용하는 경우:
type Point struct {
X, Y int
}
// 1. 작은 구조체 (복사 비용 적음)
// 2. 불변성 보장
// 3. 기본 타입처럼 동작
func (p Point) Distance(other Point) float64 {
dx := float64(p.X - other.X)
dy := float64(p.Y - other.Y)
return math.Sqrt(dx*dx + dy*dy)
}
4. 메서드 체이닝
type QueryBuilder struct {
table string
fields []string
where string
limit int
}
func NewQueryBuilder() *QueryBuilder {
return &QueryBuilder{}
}
func (qb *QueryBuilder) Table(name string) *QueryBuilder {
qb.table = name
return qb
}
func (qb *QueryBuilder) Select(fields ...string) *QueryBuilder {
qb.fields = fields
return qb
}
func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
qb.where = condition
return qb
}
func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
qb.limit = n
return qb
}
func (qb *QueryBuilder) Build() string {
return fmt.Sprintf("SELECT %s FROM %s WHERE %s LIMIT %d",
strings.Join(qb.fields, ", "),
qb.table,
qb.where,
qb.limit)
}
func methodChaining() {
query := NewQueryBuilder().
Table("users").
Select("id", "name", "email").
Where("age > 18").
Limit(10).
Build()
fmt.Println(query)
// SELECT id, name, email FROM users WHERE age > 18 LIMIT 10
}
Getter와 Setter
type Account struct {
owner string
balance float64
}
// Getter - GetX가 아닌 X 형태 (Go 관례)
func (a *Account) Owner() string {
return a.owner
}
func (a *Account) Balance() float64 {
return a.balance
}
// Setter - SetX 형태
func (a *Account) SetOwner(owner string) {
a.owner = owner
}
func (a *Account) Deposit(amount float64) error {
if amount <= 0 {
return fmt.Errorf("amount must be positive")
}
a.balance += amount
return nil
}
func (a *Account) Withdraw(amount float64) error {
if amount <= 0 {
return fmt.Errorf("amount must be positive")
}
if amount > a.balance {
return fmt.Errorf("insufficient funds")
}
a.balance -= amount
return nil
}
func getterSetter() {
account := &Account{owner: "Alice", balance: 1000}
fmt.Println("Owner:", account.Owner())
fmt.Println("Balance:", account.Balance())
account.Deposit(500)
fmt.Println("After deposit:", account.Balance()) // 1500
account.Withdraw(200)
fmt.Println("After withdrawal:", account.Balance()) // 1300
}
구조체 비교
type Point struct {
X, Y int
}
type Line struct {
Start, End Point
}
func structComparison() {
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
p3 := Point{X: 3, Y: 4}
// ✅ 모든 필드가 comparable이면 구조체도 비교 가능
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
l1 := Line{Start: p1, End: p3}
l2 := Line{Start: p1, End: p3}
fmt.Println(l1 == l2) // true
}
type NotComparable struct {
Name string
Data []int // 슬라이스는 비교 불가능
}
func notComparable() {
// nc1 := NotComparable{Name: "test", Data: []int{1, 2}}
// nc2 := NotComparable{Name: "test", Data: []int{1, 2}}
// ❌ 컴파일 에러
// fmt.Println(nc1 == nc2)
}
구조체 복사
1. 얕은 복사 (Shallow Copy)
type Person struct {
Name string
Age int
Hobbies []string // 참조 타입
}
func shallowCopy() {
p1 := Person{
Name: "Alice",
Age: 30,
Hobbies: []string{"reading", "coding"},
}
// 얕은 복사
p2 := p1
p2.Name = "Bob"
p2.Age = 25
// 슬라이스는 참조가 복사됨
p2.Hobbies[0] = "gaming"
fmt.Println(p1.Name) // Alice (변경 안됨)
fmt.Println(p1.Hobbies[0]) // gaming (변경됨!)
}
2. 깊은 복사 (Deep Copy)
func (p Person) DeepCopy() Person {
// 슬라이스 수동 복사
hobbiesCopy := make([]string, len(p.Hobbies))
copy(hobbiesCopy, p.Hobbies)
return Person{
Name: p.Name,
Age: p.Age,
Hobbies: hobbiesCopy,
}
}
func deepCopy() {
p1 := Person{
Name: "Alice",
Age: 30,
Hobbies: []string{"reading", "coding"},
}
p2 := p1.DeepCopy()
p2.Hobbies[0] = "gaming"
fmt.Println(p1.Hobbies[0]) // reading (변경 안됨)
fmt.Println(p2.Hobbies[0]) // gaming
}
디자인 패턴
1. 생성자 패턴
type User struct {
id int
name string
email string
createdAt time.Time
}
// 생성자 함수
func NewUser(name, email string) *User {
return &User{
id: generateID(),
name: name,
email: email,
createdAt: time.Now(),
}
}
func generateID() int {
return rand.Int()
}
func constructor() {
user := NewUser("Alice", "alice@example.com")
fmt.Printf("%+v\n", user)
}
2. 옵셔널 패턴 (Functional Options)
type Server struct {
host string
port int
timeout time.Duration
maxConn int
}
type ServerOption func(*Server)
func WithHost(host string) ServerOption {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) ServerOption {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) ServerOption {
return func(s *Server) {
s.timeout = timeout
}
}
func WithMaxConnections(max int) ServerOption {
return func(s *Server) {
s.maxConn = max
}
}
func NewServer(options ...ServerOption) *Server {
// 기본값 설정
server := &Server{
host: "localhost",
port: 8080,
timeout: 30 * time.Second,
maxConn: 100,
}
// 옵션 적용
for _, option := range options {
option(server)
}
return server
}
func functionalOptions() {
// 기본값 사용
s1 := NewServer()
// 일부 옵션 지정
s2 := NewServer(
WithPort(9000),
WithTimeout(60*time.Second),
)
// 모든 옵션 지정
s3 := NewServer(
WithHost("0.0.0.0"),
WithPort(3000),
WithTimeout(10*time.Second),
WithMaxConnections(500),
)
fmt.Printf("%+v\n", s1)
fmt.Printf("%+v\n", s2)
fmt.Printf("%+v\n", s3)
}
3. 빌더 패턴
type EmailBuilder struct {
to string
subject string
body string
cc []string
bcc []string
}
func NewEmailBuilder() *EmailBuilder {
return &EmailBuilder{}
}
func (eb *EmailBuilder) To(to string) *EmailBuilder {
eb.to = to
return eb
}
func (eb *EmailBuilder) Subject(subject string) *EmailBuilder {
eb.subject = subject
return eb
}
func (eb *EmailBuilder) Body(body string) *EmailBuilder {
eb.body = body
return eb
}
func (eb *EmailBuilder) CC(cc ...string) *EmailBuilder {
eb.cc = append(eb.cc, cc...)
return eb
}
func (eb *EmailBuilder) BCC(bcc ...string) *EmailBuilder {
eb.bcc = append(eb.bcc, bcc...)
return eb
}
func (eb *EmailBuilder) Build() (string, error) {
if eb.to == "" {
return "", fmt.Errorf("recipient is required")
}
if eb.subject == "" {
return "", fmt.Errorf("subject is required")
}
email := fmt.Sprintf("To: %s\nSubject: %s\n", eb.to, eb.subject)
if len(eb.cc) > 0 {
email += fmt.Sprintf("CC: %s\n", strings.Join(eb.cc, ", "))
}
if len(eb.bcc) > 0 {
email += fmt.Sprintf("BCC: %s\n", strings.Join(eb.bcc, ", "))
}
email += fmt.Sprintf("\n%s", eb.body)
return email, nil
}
func builderPattern() {
email, err := NewEmailBuilder().
To("user@example.com").
Subject("Hello").
Body("This is a test email").
CC("cc1@example.com", "cc2@example.com").
Build()
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(email)
}
4. 싱글톤 패턴
import "sync"
type Database struct {
connection string
}
var (
instance *Database
once sync.Once
)
func GetDatabaseInstance() *Database {
once.Do(func() {
fmt.Println("Creating database instance")
instance = &Database{
connection: "db://localhost:5432",
}
})
return instance
}
func singleton() {
db1 := GetDatabaseInstance()
db2 := GetDatabaseInstance()
fmt.Println(db1 == db2) // true (같은 인스턴스)
}
메모리 레이아웃과 정렬
import "unsafe"
type Compact struct {
a bool // 1 byte
b bool // 1 byte
c int32 // 4 bytes
d int64 // 8 bytes
}
type NotCompact struct {
a bool // 1 byte + 7 bytes padding
d int64 // 8 bytes
b bool // 1 byte + 3 bytes padding
c int32 // 4 bytes
}
func memoryLayout() {
fmt.Println("Compact size:", unsafe.Sizeof(Compact{})) // 16 bytes
fmt.Println("NotCompact size:", unsafe.Sizeof(NotCompact{})) // 24 bytes
// ✅ 필드를 크기순으로 정렬하면 메모리 절약
}
일반적인 실수
1. 값 리시버로 수정 시도
type Counter struct {
count int
}
// ❌ 값 리시버 - 원본이 변경 안됨
func (c Counter) IncrementBad() {
c.count++
}
// ✅ 포인터 리시버 - 원본이 변경됨
func (c *Counter) IncrementGood() {
c.count++
}
func mistake1() {
c := Counter{}
c.IncrementBad()
fmt.Println(c.count) // 0 (변경 안됨!)
c.IncrementGood()
fmt.Println(c.count) // 1 (변경됨)
}
2. 구조체 포인터 nil 체크 누락
type Config struct {
Timeout int
}
func (c *Config) GetTimeout() int {
// ❌ nil 체크 없음
// return c.Timeout // nil일 때 패닉
// ✅ nil 체크
if c == nil {
return 30 // 기본값
}
return c.Timeout
}
func mistake2() {
var config *Config
// ❌ nil 포인터 역참조
// fmt.Println(config.Timeout) // 패닉
// ✅ 메서드에서 nil 처리
fmt.Println(config.GetTimeout()) // 30
}
3. 구조체 복사 시 참조 타입 공유
type Data struct {
Values []int
}
func mistake3() {
d1 := Data{Values: []int{1, 2, 3}}
// ❌ 얕은 복사 - 슬라이스 공유
d2 := d1
d2.Values[0] = 99
fmt.Println(d1.Values[0]) // 99 (의도치 않게 변경!)
// ✅ 깊은 복사
d3 := Data{Values: make([]int, len(d1.Values))}
copy(d3.Values, d1.Values)
d3.Values[0] = 100
fmt.Println(d1.Values[0]) // 99 (변경 안됨)
}
4. 임베딩 필드 이름 충돌
type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}
func mistake4() {
c := C{
A: A{Name: "A"},
B: B{Name: "B"},
}
// ❌ 애매모호함
// fmt.Println(c.Name) // 컴파일 에러
// ✅ 명시적 접근
fmt.Println(c.A.Name) // A
fmt.Println(c.B.Name) // B
}
5. 구조체 비교 시 슬라이스/맵 포함
type Record struct {
ID int
Tags []string
}
func mistake5() {
r1 := Record{ID: 1, Tags: []string{"a", "b"}}
r2 := Record{ID: 1, Tags: []string{"a", "b"}}
// ❌ 컴파일 에러 - 슬라이스는 비교 불가
// fmt.Println(r1 == r2)
// ✅ 수동 비교
equal := r1.ID == r2.ID &&
len(r1.Tags) == len(r2.Tags)
if equal {
for i := range r1.Tags {
if r1.Tags[i] != r2.Tags[i] {
equal = false
break
}
}
}
fmt.Println("Equal:", equal)
// ✅ reflect.DeepEqual 사용
fmt.Println(reflect.DeepEqual(r1, r2))
}
6. 메서드에서 리시버 이름 일관성 없음
type Person struct {
Name string
}
// ❌ 일관성 없는 리시버 이름
func (person Person) GetName() string {
return person.Name
}
func (p Person) SetName(name string) {
p.Name = name
}
// ✅ 일관된 리시버 이름 (보통 타입의 첫 글자 소문자)
func (p Person) Name() string {
return p.Name
}
7. 구조체 필드 공개/비공개 혼동
package mypackage
type User struct {
ID int // Public (대문자 시작)
name string // Private (소문자 시작)
Email string // Public
}
func mistake7() {
user := User{
ID: 1,
// name: "Alice", // 같은 패키지에서만 접근 가능
Email: "alice@example.com",
}
// JSON 마샬링 시 private 필드는 제외됨
jsonData, _ := json.Marshal(user)
fmt.Println(string(jsonData)) // {"ID":1,"Email":"alice@example.com"}
}
구조체 JSON 처리
import (
"encoding/json"
"time"
)
type Article struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content,omitempty"`
Author string `json:"author"`
Published bool `json:"published"`
CreatedAt time.Time `json:"created_at"`
Tags []string `json:"tags,omitempty"`
}
func jsonHandling() {
article := Article{
ID: 1,
Title: "Go Structs",
Content: "Learn about structs in Go",
Author: "Alice",
Published: true,
CreatedAt: time.Now(),
Tags: []string{"go", "programming"},
}
// 마샬링 (구조체 → JSON)
jsonData, err := json.MarshalIndent(article, "", " ")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(jsonData))
// 언마샬링 (JSON → 구조체)
jsonStr := `{
"id": 2,
"title": "Go Interfaces",
"author": "Bob",
"published": false,
"created_at": "2024-01-01T00:00:00Z"
}`
var article2 Article
err = json.Unmarshal([]byte(jsonStr), &article2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("%+v\n", article2)
}
커스텀 JSON 마샬링
type CustomDate time.Time
func (cd CustomDate) MarshalJSON() ([]byte, error) {
t := time.Time(cd)
formatted := fmt.Sprintf("\"%s\"", t.Format("2006-01-02"))
return []byte(formatted), nil
}
func (cd *CustomDate) UnmarshalJSON(data []byte) error {
str := string(data)
str = strings.Trim(str, "\"")
t, err := time.Parse("2006-01-02", str)
if err != nil {
return err
}
*cd = CustomDate(t)
return nil
}
type Event struct {
Name string `json:"name"`
Date CustomDate `json:"date"`
}
func customJSON() {
event := Event{
Name: "Conference",
Date: CustomDate(time.Now()),
}
jsonData, _ := json.Marshal(event)
fmt.Println(string(jsonData))
// {"name":"Conference","date":"2024-01-01"}
}
정리
- 타입 정의:
type NewType BaseType(새 타입) vstype Alias = Type(별칭) - 구조체: 여러 필드를 묶은 복합 타입
- 제로값: var 선언 시 모든 필드가 제로값으로 초기화
- 임베딩: 상속 대신 조합으로 기능 확장 (익명 필드)
- 값 리시버: 불변, 작은 구조체에 적합
- 포인터 리시버: 수정, 큰 구조체에 적합
- 메서드 체이닝: 포인터 반환으로 체이닝 패턴 구현
- Getter:
X()형태 (GetX 아님) - Setter:
SetX()형태 - 태그: 구조체 필드에 메타데이터 추가 (JSON, DB, 검증)
- 비교: 모든 필드가 comparable이면 구조체도 비교 가능
- 복사: 기본은 얕은 복사, 깊은 복사는 수동 구현 필요
- 패턴: 생성자, 옵션, 빌더, 싱글톤
- 메모리 최적화: 필드를 크기순으로 정렬
- JSON: 태그로 마샬링/언마샬링 제어, 커스텀 구현 가능