갈루아의 반서재

Returning Multiple Values

다수의 값을 함수를 통해 반환할 수 있다.

1
2
3
4
5
6
7
func f() (intint) {
  return 56
}
 
func main() {
  x, y := f()
}
cs

이를 위해서 3가지 변경이 필요하다. 먼저, 반환형을 변경해야 하고( ,,  로 구분). 다음으로 표현식을 변경해야한다(, 로 구분). 마지막으로 := or = 의 좌측에 다수의 값이 존재할 수 있도록 대입문을 변경해야 한다. 

주로 결과와 에러값을 같이 포함해서 반환하거나 또는 성공 여부를 가리는 불린값을 같이 반환할 때 주로 쓰인다. 


Variadic Functions

Go 함수에는 특수한 형식의 함수가 존재하는데, 가변함수가 그것이다. 다음의 예를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
 
import "fmt"
 
func add(args ...intint {
  total := 0
  for _, v := range args {
    total += v
  }
  return total
}
 
func main() {
  fmt.Println(add(1,2,3))
}
cs


1
2
~/go/src/golang-book/chapter7# go run main.go
6
cs

마지막 파라메터 타입명 앞에 ... 를 넣음으로써, 이러한 타입의 파라메터가 다수 존재함을 알려줄 수 있다. 이 예제에서는 다수의 int 가 존재하는 것이다. 이를 통해 원하는 만큼이 int 를 전달할 수 있다.

이것은 fmt.Println 함수가 구현되는 방식과 동일하다. Println 함수이 어떤 타입이든 다수의 값을 가질 수 있다. 

1
func Println(a ...interface{}) (n int, err error)
cs

그리고 ints 의 일부분만 ... 을 통해서 전달할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import "fmt"
 
func add(args ...intint {
  total := 0
  for _, v := range args {
    total += v
  }
  return total
}
 
func main() {
  xs := []int{1,2,3}
  fmt.Println(add(xs...))
}
cs


Closure

함수 안에 함수를 생성하는 것도 가능하다. 

1
2
3
4
5
6
func main() {
  add := func(x, y intint {
    return x + y
  }
  fmt.Println(add(1,1))
}
cs

여기서 add 는 func(int, int) int (2개의 int 를 받아 1개의 int 를 반환해주는 함수) 타입을 가진 지역 변수이다. 이런 지역 함수를 생성할 때, 다른 지역변수에 대한 접근도 가능하다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
 
import "fmt"
 
func main() {
  x := 0
  increment := func() int {
    x++
    return x
  }
  fmt.Println(increment())
  fmt.Println(increment())
}
cs

increment 는 main 함수 단에서 정의된 x 에 1을 더한다. 이 x 변수는 increment 함수에 의해 억세스되고 변경될 수 있다. 이것이 첫 줄에는 1이 다음에는 2가 출력되는 이유다. 

위와 같이 함수와 그것이 참조하는 비국부변수를 묶어서 closure 라고 한다. 여기서는 increment 와 변수 x 가 closure 를 형성한다. 

closure 를 사용하는 하나의 방법은 다른 함수를 반환하는 함수를 작성하는 것이다. 아래는 모든 짝수를 생성하는 예제이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import "fmt"
 
func makeEvenGenerator() func() uint {
  i := uint(0)
  return func() (ret uint) {
    ret = i
    i += 2
    return
  }
}
 
func main() {
  nextEven := makeEvenGenerator()
  fmt.Println(nextEven()) // 0
  fmt.Println(nextEven()) // 2
  fmt.Println(nextEven()) // 4
}
cs

makeEvenGenerator 는 짝수를 생성하는 함수를 반환한다. 매번 호출될 때마다 지역 변수 i 에 2를 더하게 된다. 


Recursion

마지막으로 살펴볼 함수는 스스로를 호출하는 형태이다. factorial 을 계산하는 아래 함수를 보자. 

1
2
3
4
5
6
func factorial(x uintuint {
  if x == 0 {
    return 1
  }
  return x * factorial(x-1)
}
cs

factorial 는 자신을 호출한다. 즉, 재귀적으로 만드는 것이다. 

Closure 와 recursion 은 강력한 프로그래밍 테크닉으로, functional programming 의 기본 패러다임을 구성한다. 


Defer, Panic & Recover

Go 는 함수가 완료된 후 함수 호출이 이루어지게 하는 defer 라는 특수한 명령문을 가지고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
 
import "fmt"
 
func first() {
  fmt.Println("1st")
}
 
func second() {
  fmt.Println("2nd")
}
 
func main() {
  defer second()
  first()
}
cs

실행결과는 다음과 같다. 1st, 2nd 를 차례로 보여준다. 

1
2
3
4
~/go/src/golang-book/chapter7# go run main.go
1st
2nd
 
cs

defer는 리소스가 자유롭게 활용되길 원할 때 종종 사용된다. defer 를 이용하여 파일 클로징을 추후에 확인할 수 있다.

1
2
f, _ := os.Open(filename)
defer f.Close()
cs

이는 3가지 장점을 가진다. 

(1) 종료 call 과 오픈 call 이 가까이에 있어 이해하기 쉽다.

(2) 만약 함수가 다수의 반환문을 가지는 경우 (예를 들어, 하나는 if 문에 다른 하나는 else 문에 위치) Close 는 그들 모두에 앞서 발생한다. 

(3) 심지어 런타임 패닉이 발생해도 실행된다.


Panic & Recover

앞서 런타임 에러를 발생시키는 panic function 을 호출하는 함수를 이미 만든바 있다. 내장된 recover 함수를 이용해 런타임 패닉을 다룰 수 있는데, recover 는 panic을 멈추고 전달된 값을 반환한다. 다음과 같이 작성할 수 있다.

1
2
3
4
5
6
7
8
9
package main
 
import "fmt"
 
func main() {
  panic("PANIC")
  str := recover()
  fmt.Println(str)
}
cs


1
2
3
4
5
6
7
8
~/go/src/golang-book/chapter7# go run main.go
panic: PANIC
 
goroutine 1 [running]:
main.main()
        /root/go/src/golang-book/chapter7/main.go:6 +0x39
exit status 2
 
cs

하지만 recover 하려는 호출은 절대 일어나지 않는데, 그 이유는 panic 이 즉각 함수의 실행을 중지시켰기 때문이다. 대신 defer 와 짝으로 구성해야 한다. 

1
2
3
4
5
6
7
8
9
10
11
package main
 
import "fmt"
 
func main() {
    defer func() {
        str := recover()
        fmt.Println(str)    
    }()
    panic("PANIC")
}
cs


1
2
~/go/src/golang-book/chapter7# go run main.go
PANIC
cs