absl::Status — exception-free error handling
한 줄 요약:
absl::Status는 Google의 사내 표준 에러 타입이다. exception 없이 에러를 값으로 전달하고, 17개의 canonical code로 종류를 분류하며, ok 경로에서는 포인터 한 개의 크기만 차지하도록 설계되었다.
#어떤 문제를 푸는가
Google의 C++ 코드베이스는 exception을 거의 쓰지 않는다. 이유는 여러 가지가 있다.
- 성능 예측 가능성 — exception은 throw 비용이 비대칭적으로 크다.
- ABI 호환 —
-fno-exceptions빌드가 일부 환경에서 필수. - history — sub-second latency를 요구하는 시스템에서 exception이 부담.
- 명시성 — 함수 시그니처에서 에러 가능성이 드러남.
대안이 필요했다. int errno 같은 C 스타일은 type-safe가 부족하고 메시지를 담을 수 없다. std::error_code는 가볍지만 디버깅에 필요한 컨텍스트가 없다. absl::Status는 그 사이를 메운다.
#기본 사용
#include "absl/status/status.h"
absl::Status ValidateInput(const Request& req) { if (req.name().empty()) { return absl::InvalidArgumentError("name must not be empty"); } if (req.age() < 0) { return absl::OutOfRangeError( absl::StrCat("age must be non-negative, got ", req.age())); } return absl::OkStatus();}
void Handle(const Request& req) { absl::Status s = ValidateInput(req); if (!s.ok()) { LOG(ERROR) << s; return; } // ok path Process(req);}핵심 패턴.
- 성공은
OkStatus(). - 실패는
XxxError(msg)형태의 factory. - 호출자는
s.ok()로 검사 또는 매크로(다음 편)로 자동 전파.
#17개의 Canonical Code
17개 코드를 색으로 분류해 보면 다음과 같다.
namespace absl {enum class StatusCode : int { kOk = 0, kCancelled = 1, kUnknown = 2, kInvalidArgument = 3, kDeadlineExceeded = 4, kNotFound = 5, kAlreadyExists = 6, kPermissionDenied = 7, kResourceExhausted = 8, kFailedPrecondition = 9, kAborted = 10, kOutOfRange = 11, kUnimplemented = 12, kInternal = 13, kUnavailable = 14, kDataLoss = 15, kUnauthenticated = 16,};} // namespace absl이 코드는 gRPC의 error code와 1
일치한다. 같은 분류 체계.#각 코드의 의미
| 코드 | 언제 |
|---|---|
OK | 성공 |
Cancelled | 호출자가 취소 (CTRL+C, deadline) |
Unknown | 분류 불가 (다른 시스템에서 변환 시 fallback) |
InvalidArgument | 입력이 형식상 잘못 (검증 실패) |
DeadlineExceeded | 시간 초과 |
NotFound | 요청한 리소스가 없음 |
AlreadyExists | 만들려는 리소스가 이미 있음 |
PermissionDenied | 권한 부족 (인증은 OK, 권한이 부족) |
ResourceExhausted | quota, rate limit 등 |
FailedPrecondition | 시스템 상태가 잘못 (전제 위반) |
Aborted | 동시성 충돌, 트랜잭션 abort |
OutOfRange | 값이 범위 밖 (validation의 하위) |
Unimplemented | 기능 미구현 |
Internal | 내부 invariant 위반 (보통 버그) |
Unavailable | 일시적 장애, retry 가능 |
DataLoss | 데이터 손상/유실 |
Unauthenticated | 인증 실패 |
코드 선택은 의외로 미묘하다. FailedPrecondition vs InvalidArgument vs OutOfRange의 차이가 자주 헷갈린다. gRPC 문서가 가이드라인을 제공한다.
#API 메서드
absl::Status s = SomeOp();
// 검사bool ok = s.ok();absl::StatusCode code = s.code();absl::string_view msg = s.message();
// 출력 — 디버깅용LOG(INFO) << s; // "INVALID_ARGUMENT: name must not be empty"std::string str = s.ToString();
// 비교if (s.code() == absl::StatusCode::kNotFound) { /*...*/ }#내부 구현 — 8바이트만으로
// absl/status/status.h 의사 코드class Status {public: // ...private: uintptr_t rep_; // 하위 비트로 ok/error 구분 // ok이면 특수 값 (0 또는 특정 마커) // error이면 heap-allocated payload를 가리키는 포인터};
static_assert(sizeof(Status) == sizeof(void*));가장 중요한 최적화. ok status는 heap 할당 없이 stack에 그대로. 8바이트.
inline Status OkStatus() { return Status(); // rep_ = 0 (또는 ok marker)}
inline bool Status::ok() const { return rep_ == kOkRep; // 단순 비교}성공 경로가 hot path인 대부분의 코드에서, Status는 사실상 비용이 없다. error 경로에서만 heap 할당이 일어난다.
#error 경로
error status를 만들면 다음이 일어난다.
absl::Status MakeError(StatusCode code, absl::string_view msg) { // 1. heap allocation auto* rep = new StatusRep{ code, std::string(msg), // payload map (보통 비어 있음) }; // 2. rep_에 포인터 저장 (low bit로 error 표시) return Status(reinterpret_cast<uintptr_t>(rep) | 1);}StatusRep는 reference-counted. status를 복사해도 같은 payload를 가리킨다.
#Factory 함수
각 canonical code에 대응하는 factory가 있다.
absl::Status absl::OkStatus();absl::Status absl::CancelledError(absl::string_view msg);absl::Status absl::UnknownError(absl::string_view msg);absl::Status absl::InvalidArgumentError(absl::string_view msg);absl::Status absl::DeadlineExceededError(absl::string_view msg);absl::Status absl::NotFoundError(absl::string_view msg);absl::Status absl::AlreadyExistsError(absl::string_view msg);absl::Status absl::PermissionDeniedError(absl::string_view msg);absl::Status absl::ResourceExhaustedError(absl::string_view msg);absl::Status absl::FailedPreconditionError(absl::string_view msg);absl::Status absl::AbortedError(absl::string_view msg);absl::Status absl::OutOfRangeError(absl::string_view msg);absl::Status absl::UnimplementedError(absl::string_view msg);absl::Status absl::InternalError(absl::string_view msg);absl::Status absl::UnavailableError(absl::string_view msg);absl::Status absl::DataLossError(absl::string_view msg);absl::Status absl::UnauthenticatedError(absl::string_view msg);대응되는 predicate도 있다.
bool absl::IsCancelled(const Status&);bool absl::IsInvalidArgument(const Status&);bool absl::IsNotFound(const Status&);// ... 모든 코드에 대해#코드 리뷰 포인트
#적절한 code 선택
// 회피 — 모든 에러를 Internal로return absl::InternalError("something failed");
// Good — 의미 있는 분류if (input.empty()) return absl::InvalidArgumentError("input is empty");if (!exists) return absl::NotFoundError(absl::StrCat("not found: ", key));if (!has_permission) return absl::PermissionDeniedError("admin only");Internal은 “이건 진짜 내부 버그”일 때만. 외부 입력으로 인한 에러는 InvalidArgument / OutOfRange / FailedPrecondition.
#메시지에 충분한 컨텍스트
// 회피 — 정보 부족return absl::NotFoundError("not found");
// Good — 무엇이 어디서 누락됐는지return absl::NotFoundError( absl::StrCat("user not found: id=", user_id, " in table=", table));#Status를 무시하지 말 것
// 회피SomeOp(); // Status 반환값을 버림 — 경고도 안 남
// Goodabsl::Status s = SomeOp();if (!s.ok()) { LOG(ERROR) << s; return s;}absl::Status는 [[nodiscard]]이므로 컴파일러가 무시를 경고한다. 그래도 명시적 처리가 권장.
#자주 보는 안티패턴
// 회피 — bool 반환 + 별도 에러 메시지bool DoIt(std::string* err_msg);if (!DoIt(&msg)) { LOG(ERROR) << msg;}// Status가 둘을 한 번에 표현.
// Goodabsl::Status DoIt();if (auto s = DoIt(); !s.ok()) LOG(ERROR) << s;// 회피 — exception을 catch 후 Status로 변환을 매번try { DoExternal();} catch (const std::exception& e) { return absl::InternalError(e.what());}// catch는 Abseil 코드의 끝(public API boundary)에서만.
// Good — 가능하면 exception 안 던지는 라이브러리 선택// 어쩔 수 없으면 wrapper에서 한 번만 변환// 회피 — Status를 const Status&가 아닌 값으로 받아 modifyabsl::Status TryIt() { absl::Status s = SomeOp(); s = absl::OkStatus(); // 원본 무시 return s;}
// Goodabsl::Status TryIt() { if (auto s = SomeOp(); !s.ok()) { // 처리 } return absl::OkStatus();}#std와의 비교
| 도구 | 장점 | 단점 |
|---|---|---|
std::error_code | 가벼움 (16바이트), 표준 | 메시지 없음, error category 등록 복잡 |
| C++ exception | 컴파일러 통합 | runtime 비용, ABI 영향 |
bool + out param | 단순 | 메시지·코드 분리 |
absl::Status | 메시지·코드·payload 통합, OK 경로 zero-overhead | Abseil 의존 |
std::expected (C++23)이 일부 역할을 대체할 수 있지만, canonical code 체계와 payload 시스템은 Abseil 고유.
#정리
absl::Status는 exception 없는 Google 표준 에러 타입.- ok 경로는 8바이트 stack, error 경로는 heap-allocated payload.
- 17개의 canonical code (gRPC와 일치).
- factory 함수와 predicate가 모든 코드에 대해 제공.
Internal은 진짜 내부 버그일 때만. 외부 입력 에러는InvalidArgument/NotFound등.
#다음 편
Part 3-02에서 absl::StatusOr<T>를 본다. “값이거나 에러”를 한 type으로 표현하는 패턴이고, 함수가 보통 반환하는 값과 에러를 합쳐서 다룬다.
#관련 항목
Abseil Code Review · 15 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_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
Part 3-03: RETURN_IF_ERROR와 ASSIGN_OR_RETURN — 에러 전파를 한 줄로. 매크로 expansion 분석과 안전한 사용법.
같은 시리즈에서 이어 읽기
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와 연동.
같은 시리즈에서 이어 읽기
이 글을 참조하는 글 (9)
- Google 스타일의 Abseil 사용 패턴 — Abseil Code Review
- Abseil LOG·VLOG·CHECK 분석 — Abseil Code Review
- absl::Status ↔ exception 변환 패턴 — Abseil Code Review
- absl::Status payload — 구조화된 에러 컨텍스트 — Abseil Code Review
- absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR — Abseil Code Review
- absl::StatusOr<T> — 값 또는 에러 — Abseil Code Review
- Abseil ABSL_PREDICT_TRUE/FALSE — branch hint — Abseil Code Review
- Abseil 개요 — Google이 std를 보완한 이유 — Abseil Code Review
- folly::Future thenValue·thenError·thenTry — continuation 체인 분석 — Folly Code Review