absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
한 줄 요약:
RETURN_IF_ERROR와ASSIGN_OR_RETURN은 Status/StatusOr의 장황한 에러 전파를 한 줄로 줄인다. Google 사내에서는 사실상 표준 패턴이고, 매크로 expansion에 몇 가지 미묘한 트릭이 들어 있다.
#어떤 문제를 푸는가
absl::Status / absl::StatusOr<T>를 그대로 쓰면 코드의 절반이 에러 검사가 된다.
absl::Status Process(const std::string& path) { absl::Status s1 = ValidatePath(path); if (!s1.ok()) return s1;
auto r1 = LoadConfig(path); if (!r1.ok()) return r1.status(); Config config = *std::move(r1);
auto r2 = CreateServer(config); if (!r2.ok()) return r2.status(); Server server = *std::move(r2);
return RunServer(&server);}매크로로 단축.
absl::Status Process(const std::string& path) { RETURN_IF_ERROR(ValidatePath(path)); ASSIGN_OR_RETURN(Config config, LoadConfig(path)); ASSIGN_OR_RETURN(Server server, CreateServer(config)); return RunServer(&server);}같은 의미. 노이즈가 4분의 1.
#RETURN_IF_ERROR
#define RETURN_IF_ERROR(expr) \ do { \ const absl::Status _absl_status_ = (expr); \ if (ABSL_PREDICT_FALSE(!_absl_status_.ok())) return _absl_status_; \ } while (0)핵심 구조.
do { ... } while (0)패턴 — if-else 안에서 안전하게 쓸 수 있게.const Status _absl_status_임시 변수에 캐싱 — 인자가 함수 호출이면 두 번 평가되지 않음.ABSL_PREDICT_FALSE— ok 경로가 hot path임을 컴파일러에 힌트.
매크로 이름은 내부 식별자(_absl_status_)를 쓴다. 사용자 코드의 변수와 충돌하지 않게.
#사용 예시
RETURN_IF_ERROR(DoStep1());RETURN_IF_ERROR(DoStep2());
if (cond) RETURN_IF_ERROR(DoIfCond()); // do-while로 안전
for (int i = 0; i < n; ++i) { RETURN_IF_ERROR(DoOne(i));}#ASSIGN_OR_RETURN
StatusOr 버전. 값 추출과 에러 전파를 동시에.
// 기본 형태ASSIGN_OR_RETURN(Config config, LoadConfig(path));
// 풀면auto _absl_tmp_ = LoadConfig(path);if (!_absl_tmp_.ok()) return _absl_tmp_.status();Config config = *std::move(_absl_tmp_);매크로 expansion의 진짜 모습은 더 복잡하다.
// 의사 코드#define ASSIGN_OR_RETURN(lhs, rexpr) \ ASSIGN_OR_RETURN_IMPL(\ ABSL_INTERNAL_CONCAT(_status_or_, __LINE__), lhs, rexpr)
#define ASSIGN_OR_RETURN_IMPL(statusor, lhs, rexpr) \ auto statusor = (rexpr); \ if (ABSL_PREDICT_FALSE(!statusor.ok())) return statusor.status(); \ lhs = *std::move(statusor)__LINE__을 활용해 같은 함수 안에서 여러 번 호출해도 변수 이름이 충돌하지 않게 한다.
#사용 예시
// 단순ASSIGN_OR_RETURN(int n, ParseInt("42"));
// 객체 멤버 접근ASSIGN_OR_RETURN(Config config, LoadConfig(path));LOG(INFO) << "loaded: " << config.name();
// 기존 변수에 할당 — auto 없이Config config;ASSIGN_OR_RETURN(config, LoadConfig(path)); // 컴파일 에러// 매크로가 `auto`로 가정하지 않음. 새 변수 선언이 보통.#두 매크로의 조합
absl::Status Pipeline(const std::string& path) { RETURN_IF_ERROR(ValidatePath(path)); ASSIGN_OR_RETURN(Config config, LoadConfig(path)); RETURN_IF_ERROR(config.Validate()); ASSIGN_OR_RETURN(Server server, CreateServer(config)); RETURN_IF_ERROR(server.Init()); return RunServer(&server);}선형 흐름이 그대로 드러난다. 에러 처리는 컴파일러가 자동으로.
#Abseil 외부 정의
흥미롭게도 ASSIGN_OR_RETURN은 Abseil 공식에 포함되지 않는다. Google 사내에는 있지만 오픈소스 Abseil에는 없다. 이유는 코드베이스마다 약간씩 다른 변형이 있기 때문이라고 알려져 있다.
다음과 같이 직접 정의하는 것이 일반적이다.
// 사용자 프로젝트의 status_macros.h#define RETURN_IF_ERROR(expr) \ do { \ const absl::Status _absl_status_ = (expr); \ if (ABSL_PREDICT_FALSE(!_absl_status_.ok())) return _absl_status_; \ } while (0)
#define ASSIGN_OR_RETURN_IMPL(statusor, lhs, rexpr) \ auto statusor = (rexpr); \ if (ABSL_PREDICT_FALSE(!statusor.ok())) return statusor.status(); \ lhs = *std::move(statusor)
#define ASSIGN_OR_RETURN(lhs, rexpr) \ ASSIGN_OR_RETURN_IMPL(STATUS_MACROS_CONCAT(_statusor_, __COUNTER__), lhs, rexpr)
#define STATUS_MACROS_CONCAT_IMPL(x, y) x##y#define STATUS_MACROS_CONCAT(x, y) STATUS_MACROS_CONCAT_IMPL(x, y)gRPC, TensorFlow, Envoy 등 큰 프로젝트가 각자 정의해 쓴다. 표준화되지 않은 이유가 일종의 사회적 합의로 받아들여진다.
#추가 변형
#RETURN_IF_ERROR with prefix
에러 메시지에 컨텍스트 추가.
#define RETURN_IF_ERROR_PREFIX(expr, prefix) \ do { \ absl::Status _s = (expr); \ if (ABSL_PREDICT_FALSE(!_s.ok())) { \ return absl::Status(_s.code(), \ absl::StrCat(prefix, ": ", _s.message())); \ } \ } while (0)
// 사용RETURN_IF_ERROR_PREFIX(LoadConfig(path), "loading config");// 에러: "loading config: file not found"#ASSIGN_OR_RETURN with stream
RETURN_IF_ERROR(expr) << "context"; 형태로 stream syntax를 지원하는 변형도 있다. 구현은 더 복잡해진다.
#코드 리뷰 포인트
// 회피 — 매크로 안의 인자가 부작용 있는 함수RETURN_IF_ERROR(IncrementAndCheck());// 보통은 안전 — _absl_status_에 캐싱됨. 한 번만 호출.// 다만 매크로 정의에 따라 다를 수 있으니 확인.// 회피 — 매크로 안의 인자가 너무 길어 가독성 떨어짐ASSIGN_OR_RETURN(auto result, SomeVeryLongFunctionName(argument1, argument2, argument3, argument4));// 한 줄에 모든 게 들어가 읽기 힘듦.
// Good — 호출을 분리auto temp = SomeVeryLongFunctionName(arg1, arg2, arg3, arg4);ASSIGN_OR_RETURN(auto result, std::move(temp));// 또는 그냥 풀어서auto r = SomeVeryLongFunctionName(...);if (!r.ok()) return r.status();auto result = *std::move(r);// 회피 — 매크로를 if 조건 안에if (RETURN_IF_ERROR(...)) { ... } // 컴파일 에러// do-while로 감싸진 매크로는 expression이 아님.리뷰에서:
- 에러 컨텍스트가 충분한가 — prefix 매크로 사용 검토.
- 매크로 안의 표현식이 너무 길지 않은가.
- 에러를 silent하게 swallow하지 않는가 — 매크로 사용은 그 자체로 전파.
#자주 보는 안티패턴
// 회피 — 매크로를 안 쓰고 매번 손으로auto r = Op();if (!r.ok()) return r.status();auto v = *r;// 자주 반복되면 매크로 사용 검토.
// GoodASSIGN_OR_RETURN(auto v, Op());// 회피 — 매크로 안에서 추가 처리 시도ASSIGN_OR_RETURN(int x, Parse(s) + 1); // 어색// `Parse(s) + 1`이 StatusOr<int>를 반환? operator+ 정의 안 됨.
// GoodASSIGN_OR_RETURN(int x, Parse(s));int y = x + 1;// 회피 — 매크로 매번 작성absl::Status MyFunc() { auto s = ...; if (!s.ok()) return s; // 5번 더 반복}// 매번 손으로. 매크로로 옮길 것.// 회피 — 매크로 안의 변수 이름과 사용자 변수 충돌absl::Status _absl_status_ = ...; // 매크로 내부 이름과 충돌RETURN_IF_ERROR(Op()); // 일부 구현에서 깨질 수 있음// 매크로 내부 이름을 모르면 충돌 위험. _ 접두사 변수는 보통 reserved.#std::expected의 monadic operations와 비교
매크로와 monadic 메서드는 같은 그림을 다르게 표현한 것이다.
C++23의 std::expected는 매크로 대신 monadic operations를 제공한다.
// std::expected — C++23std::expected<int, MyError> r = ParseInt(s) .transform([](int n) { return n * 2; }) .and_then([](int n) { return Validate(n); });Abseil은 매크로 길을 택했다. 이유는 두 가지로 추정.
- C++14 호환 — lambda overhead, generic lambda 등.
- early return의 명시성 —
RETURN_IF_ERROR는 코드에 return이 보임.
C++23 이후로는 expected의 monadic이 더 깔끔할 수 있다. 그러나 StatusOr를 쓰는 코드베이스에서는 매크로가 사실상 표준.
#정리
RETURN_IF_ERROR와ASSIGN_OR_RETURN은 Status/StatusOr 에러 전파의 표준 매크로.- 오픈소스 Abseil에는 포함되어 있지 않음. 프로젝트별로 정의하는 것이 일반적.
- 내부적으로
do-while(0),__LINE__/__COUNTER__활용으로 안전성 확보. ABSL_PREDICT_FALSE로 ok 경로 hot path 힌트.- 표현식이 길어지면 매크로 분리하거나 풀어 쓸 것.
#다음 편
Part 3-04에서 Status payload를 본다. 에러 메시지 외에 구조화된 컨텍스트를 어떻게 추가하는지, URL-based key 시스템이 어떻게 충돌을 방지하는지.
#관련 항목
Abseil Code Review · 17 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl::Status — exception-free error handling
Part 3-01: absl::Status — Google이 exception 없이 production C++ 에러를 다루는 방법. canonical error code, payload, 내부 표현.
같은 시리즈에서 이어 읽기
absl::Status ↔ exception 변환 패턴
Part 3-05: Status와 exception/std::error_code/gRPC status 사이 변환 — 라이브러리 경계에서의 안전한 처리.
같은 시리즈에서 이어 읽기
absl::Status payload — 구조화된 에러 컨텍스트
Part 3-04: Status payload — URL-based key로 구조화된 컨텍스트를 첨부, gRPC error_details와 연동.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (6)
- absl::Cleanup — 함수 종료 시 실행 보장 — Abseil Code Review
- Abseil 자주 보는 anti-pattern — Abseil Code Review
- absl::Status ↔ exception 변환 패턴 — Abseil Code Review
- absl::Status payload — 구조화된 에러 컨텍스트 — Abseil Code Review
- absl::StatusOr<T> — 값 또는 에러 — Abseil Code Review
- absl::Status — exception-free error handling — Abseil Code Review