프로그래밍선임 Go 개발자

Go에서 구조체에 대한 값 의미(value semantic)는 어떻게 작동하며, 구조체와 그 슬라이스를 전달할 때 발생하는 예상치 못한 점은 무엇인가요?

Hintsage AI 어시스턴트로 면접 통과

답변.

문제의 역사:

Go는 명시적인 값 의미(value semantic)를 가진 언어로 설계되었습니다. 구조체(struct)를 포함하여 대부분의 값이 호출 시 복사되지만, 포인터와 슬라이스(slice)는 제외됩니다. 이는 논리를 단순화하고 안전성을 높였지만 여러 가지 "함정"을 초래했습니다.

문제:

개발자들은 종종 함수에 구조체를 전달할 때 그 변경 사항이 "외부"에서도 보일 것이라고 예상합니다. 그러나 모든 내용이 복사됩니다(내부 필드도 값에 의해!). 슬라이스와 맵은 "내용물"이 아닌 "용기"만 복사되는 다른 동작을 보입니다.

해결책:

변경이 예상되는 경우 큰 구조체는 포인터를 통해 전달하세요. 슬라이스는 내용물이 아닌 설명자(length, capacity, pointer)만 복사됩니다. 원본 슬라이스의 변경(인덱스를 통해)은 외부에서 보입니다. 구조체는 모든 것이 복사됩니다:

type Point struct { X, Y int } func move(p Point) { p.X = 100 } func movePtr(p *Point) { p.X = 100 } func demo() { pt := Point{10, 10} move(pt) fmt.Println(pt.X) // 10 movePtr(&pt) fmt.Println(pt.X) // 100 }

주요 특징:

  • struct는 포인터가 아닐 경우 항상 값으로 복사됩니다.
  • 슬라이스와 맵은 "헤드"(설명서)만 복사됩니다.
  • 포인터 또는 값으로 전송할 때의 올바른 관리가 코드 예측 가능성의 열쇠입니다.

트릭 질문.

함수 내부에서 구조체를 변경하면 원본도 변경되나요?

아니요, 구조체가 값으로 전달되면 변경 사항은 로컬입니다.

type User struct {Name string} func f(u User) {u.Name = "Ann"}

함수 내부에서 슬라이스의 요소를 변경하면 원본도 변경되나요?

네. 슬라이스는 공유 배열에 대한 "뷰"입니다. 요소를 변경하면 원본 데이터도 변경됩니다.

func f(s []int) {s[0] = 99}

함수 내부에서 생성된 슬라이스를 반환하면 어떻게 되나요?

슬라이스의 "헤드"는 복사되지만, 기본 배열은 여전히 접근 가능합니다. 외부에서 참조를 저장하지 않으면 데이터는 GC에 의해 수집될 수 있습니다.

일반적인 오류 및 안티 패턴

  • 값으로 전달될 때 암묵적으로 구조체를 전달함
  • 다른 언어처럼 수정 가능한 참조 의미(modifiable-reference semantics)를 기대함
  • 슬라이스 작업 중 오류 (예: 기본 배열이 공유됨을 잊음)

실제 사례

부정적인 케이스

User 구조체를 값으로 처리하는 함수가 있었고, 변경 사항이 돌아오지 않아 버그를 찾기 어려웠습니다.

장점:

  • 안전함 — 원본을 변경하지 않음 (필요할 경우)

단점:

  • 명백하지 않은 버그: 함수가 원본을 수정하지 않음

긍정적인 케이스

큰 구조체는 명시적으로 포인터로 전달되고 슬라이스에 대한 동작이 항상 주석으로 처리되거나 확인됩니다. 혼란이 없어 모든 사람이 값 의미(value-semantic)를 예상합니다.

장점:

  • 예측 가능성, 유지 관리의 용이함
  • 더 안전함

단점:

  • 주의가 필요하고, 변수를 위한 명시적 포인터가 종종 필요함