C에도 Zig와 비슷한 defer 가 도입될 예정임. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3928.pdf.
Go의 defer는 함수 단위, Zig와 C의 defer는 블록 단위로 동작한다는 차이가 있음. 반복문 안에서 사용하면 Go는 나중에 실행할 작업을 힙에 할당하며 제한 없이 쌓을 수 있지만, Zig와 C는 매 반복마다 defer 문을 실행하므로 일정한 공간만 사용함.
개인적으로 defer가 코드를 그리 많이 깔끔하게 만들어 주지는 않는다고 봄. 컴파일러 확장으로 각자 구현한 버전이 이미 널리 쓰이고 있었던 것도 표준화 이유 중 하나임. 표준 위원회로서는 표준 형태로 제공할지, 비표준 형태로 남겨 둘지를 선택해야 했음. 해당 표준 회의에 직접 참석했음.
goto와 defer의 우열 대신 제어 흐름 그래프의 구조만 살펴보려 함. 오류 처리용 goto는 여러 단계의 중첩을 빠져나갈 수 있는 구조적 프로그래밍으로 그대로 옮길 수 있음.
글의 예제를 Rust로 옮기면 'err_mem이라는 레이블이 붙은 블록에서 파일 디스크립터를 열고, fd == -1이면 break 'err_mem;으로 빠져나오게 하면 됨. 성공하면 블록 안에서 반환하고, 블록 밖에서는 메모리를 해제한 뒤 실패를 반환하는 구조임. Wasm에서도 바깥 블록으로 향하는 br 또는 br_if로 똑같이 표현 가능함.
따라서 오류 처리에 goto를 쓰면 비구조적이고 환원 불가능한 제어 흐름이 되고, 다단계 탈출을 쓰면 구조적이고 환원 가능한 흐름이 되는 것은 아님. 실제 차이는 블록과 다단계 탈출을 사용할 때 탈출 지점이 늘수록 구문 중첩과 들여쓰기가 깊어진다는 데 있음. 이것도 언어의 구문을 다르게 설계하면 바꿀 수 있음. C에서 오류 처리에 goto를 계속 쓴다면, 다음에 누군가 반대할 때 이 논리를 활용할 수 있음.
C보다 더 제한적인 goto 를 언어에서 실험해 보면 흥미로울 듯함. 함수 밖으로 이동하지 못하게 하는 것에 더해, 뒤로 이동하거나 블록 안으로 진입하는 것도 금지하는 형태임.
이런 규칙이면 환원 가능한 제어 흐름을 보장하고, 위의 레이블을 붙인 break 방식과 정말로 동등해질 것으로 봄. defer보다 이것이야말로 C식 오류 처리의 궁극적인 후계자라고 생각함.