[Go] 3. struct, method, dictionary

struct

구조체(Struct)는 여러 필드를 하나로 묶어서 새로운 타입을 정의하는 방법이다.

 

기본 구조체 정의

package main

import "fmt"

type person struct {
	name string
	age int
	favFood []string
}

func main() {
	favFood := []string{"kimchi, ramen"}
	jihyun := person{name: "jihyun", age: 25, favFood: favFood}
	fmt.Println(jihyun)
}

 

출력 결과

{jihyun 25 [kimchi, ramen]}

 


 

Methods

메서드는 특정 타입에 속한 함수다. Receiver를 통해 구조체와 연결된다.

 

구조체와 생성자 함수

accounts.go

package accounts

// Account struct
type Account struct {
	owner string
	balance int
}

// NewAccount creates Account
func NewAccount(owner string) *Account {
	account := Account{owner: owner, balance: 0}
	return &account
}

 

main.go

package main

import (
	"/study-go-language/accounts"
	"fmt"
)

func main() {
	account := accounts.NewAccount("jihyun")
	fmt.Println(account)
}

 

출력 결과

&{jihyun 0}

 

&가 출력되는 이유는 NewAccount가 포인터를 반환하기 때문이다.

 


 

Value Receiver의 문제점

accounts.go

package accounts

type Account struct {
	owner string
	balance int
}

func NewAccount(owner string) *Account {
	account := Account{owner: owner, balance: 0}
	return &account
}

// Deposit x amount to the account
func (a Account) Deposit(amount int) { // receiver method: (a Account)
	a.balance += amount
}

// Balance returns the account balance
func (a Account) Balance() int {
	return a.balance
}

 

main.go

package main

import (
	"/study-go-language/accounts"
	"fmt"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)
	fmt.Println(account.Balance())
}

 

출력 결과

0

 

예상과 달리 결과는 10이 아닌 0이 나온다. 이유가 뭘까?

 

Go는 (a Account)에서 구조체의 복사본을 만든다. 따라서 Deposit 메서드에서 수정한 balance는 복사본의 값이며, 원본 account는 변경되지 않는다.

 


 

Pointer Receiver로 해결

누군가가 account.Deposit()을 호출한면, account를 복사하지 말고 Deposit method를 호출한 account를 사용하도록 해야 한다.

package accounts

type Account struct {
	owner string
	balance int
}

func NewAccount(owner string) *Account {
	account := Account{owner: owner, balance: 0}
	return &account
}

// Deposit x amount to the account
func (a *Account) Deposit(amount int) { // receiver method: (a Account)
	a.balance += amount
}

// Withdraw x amount from the account
func (a *Account) Withdraw(amount int) {
	a.balance -= amount
}

// Balance returns the account balance
func (a Account) Balance() int {
	return a.balance
}

 

package main

import (
	"/study-go-language/accounts"
	"fmt"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)
	fmt.Println(account.Balance())
	account.Withdraw(20)
	fmt.Println(account.Balance())
}


출력 결과

10
-10

 

이처럼 복사본이 아닌 실제 객체를 다루고 싶다면, 포인터 receiver(Account)를 사용해야 한다.

 


 

Error Handling

Go에는 try-catch나 exception이 없다. 에러 처리를 직접 구현해야 한다.

 

에러 처리 방법 1 - log.Fatalln

package main

import (
	"fmt"
	"log"

	"/study-go-language/accounts"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)
	fmt.Println(account.Balance())
	
	err := account.Withdraw(20)
	if err != nil {
		log.Fatalln(err) // 프로그램 종료
	}
	fmt.Println(account.Balance())
}


출력 결과

10
2025/11/08 14:42:01 Can't withdraw more than the current balance
exit status 1

 


 

에러 처리 방법 2 - 에러 출력 후 계속 실행

package main

import (
	"fmt"
	"log"

	"/study-go-language/accounts"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)
	fmt.Println(account.Balance())

	err := account.Withdraw(20)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(account.Balance())
}


출력 결과

10
Can't withdraw more than the current balance
10

 


 

에러 변수 선언 컨벤션

에러 변수는 err로 시작하는 것이 Go의 관례다.

var errNoMoney = errors.New("Can't withdraw more than the current balance")

 


 

추가 메서드 구현

Owner 관련 메서드

 

// ChangeOwner changes the account owner
func (a *Account) ChangeOwner(newOwner string) {
	a.owner = newOwner
}

// Owner returns the account owner
func (a Account) Owner() string {
	return a.owner
}

 

package main

import (
	"fmt"
	"../study-go-language/accounts"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)

	err := account.Withdraw(20)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(account.Balance(), account.Owner())
}

 

 

출력 결과

Can't withdraw more than the current balance
10 jihyun

 

 


 

String() 메서드로 출력 커스터마이징

Go에서 String() 메서드를 구현하면 fmt.Println으로 출력할 때 자동으로 호출된다. (Java의 toString()과 유사)

func (a Account) String() string {
	return fmt.Sprint(a.Owner(), "'s account.\nHas: ", a.Balance())
}

 

package main

import (
	"fmt"
	"../study-go-language/accounts"
)

func main() {
	account := accounts.NewAccount("jihyun")
	account.Deposit(10)
	fmt.Println(account)
}

 

출력 결과

jihyun's account.
Has: 10

 


 

Dictionary 구현

Map을 기반으로 한 Dictionary 타입을 만들어보자.

 

Dictionary 타입 정의

 

package mydict

import "errors"

// Dictionary type
type Dictionary map[string]string

var (
    errNotFound   = errors.New("Not Found")
    errCantUpdate = errors.New("Can't update non-existing word")
    errWordExists = errors.New("That word already exists")
)

 


 

Search 메서드

func (d Dictionary) Search(word string) (string, error) {
	value, exists := d[word]
	if exists {
		return value, nil
	}
	return "", errNotFound
}

 

Map에서 값을 조회할 때 두 번째 반환값(exists)은 해당 키가 존재하는지 여부를 나타낸다.


 

Add 메서드

func (d Dictionary) Add(word, def string) error {
	_, err := d.Search(word)
	switch err {
		case errNotFound:
			d[word] = def
		case nil:
			return errWordExists
	}
	return nil
}

 

package main

import (
	"fmt"
	"/study-go-language/mydict"
)

func main() {
    dictionary := mydict.Dictionary{}
    word := "hello"
    definition := "Greeting"
    
    err := dictionary.Add(word, definition)
    if err != nil {
        fmt.Println(err)
    }
    
    hello, _ := dictionary.Search(word)
    fmt.Println(hello)

    err2 := dictionary.Add(word, definition)
    if err2 != nil {
        fmt.Println(err2)
    }
}


출력 결과

Greeting
That word already exists

 


 

Update 메서드

func (d Dictionary) Update(word, def string) error {
	_, err := d.Search(word)
	switch err {
	case nil:
		d[word] = def
	case errNotFound:
		return errCantUpdate
	}
	return nil
}

 

package main

import (
	"fmt"
	"/study-go-language/mydict"
)

func main() {
	dictionary := mydict.Dictionary{}
	baseWord := "hello"
	dictionary.Add(baseWord, "First")
	err := dictionary.Update(baseWord, "Second")
	if err != nil {
		fmt.Println(err)
	}

	word, _ := dictionary.Search(baseWord)
	fmt.Println(word)
}


출력 결과

Second

 


 

Delete 메서드

func (d Dictionary) Delete(word, def string) {
	delete(d, word)
}

 

package main

import (
	"fmt"
	"/study-go-language/mydict"
)

func main() {
	dictionary := mydict.Dictionary{}
	baseWord := "hello"
	dictionary.Add(baseWord, "First")
	dictionary.Search(baseWord)
	dictionary.Delete(baseWord)

	word, err := dictionary.Search(baseWord)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(word)
}


출력 결과

Not Found

 


 

핵심 정리

- Value Receiver: 구조체 복사본을 사용 (읽기 전용에 적합)
- Pointer Receiver: 원본 구조체를 직접 수정 (상태 변경에 필수)
- 에러 처리: 함수가 error 타입을 반환하도록 설계
- String() 메서드: 출력 형식을 커스터마이징
- Map 기반 타입: 기존 타입에 메서드를 추가해 새로운 타입 생성 가능

 

 

아래 강의를 통해 학습한 내용을 바탕으로 작성했습니다.

https://nomadcoders.co/go-for-beginners

 

Go 프로그래밍 언어 무료 강의

Channel, Goroutines 등 Go의 핵심 개념을 익히고, 간단한 스크래핑 서비스를 만들며 실습 위주로 기초부터 배웁니다. 17,000명이 선택한 인기 무료 입문 강의로, 지금 Go 입문을 시작하세요!

nomadcoders.co

 

https://go.dev/doc/#references

 

Documentation - The Go Programming Language

Documentation The Go programming language is an open source project to make programmers more productive. Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and netwo

go.dev

 

'Language > Go' 카테고리의 다른 글

[Go] 4. URL Checker & Go routines & Channel  (0) 2025.11.09
[Go] 2. pointer, map, arrays  (0) 2025.11.08
[Go] 1. package, function, for, if, switch  (0) 2025.11.07