본문으로 건너뛰기
Folly Code Review · 69/89

folly::Expected — 결과 또는 오류

· Hawk · 3분 읽기

한 줄 요약: Expected<T, E>는 함수의 정상 결과 또는 오류 코드를 하나의 값으로 표현한다. C++23 std::expected의 선구자이자 absl::StatusOr<T>의 사촌으로 monadic 조합이 가능한 모델이다.

#동기

C++의 오류 표현은 셋이다.

  1. 예외(throw) — 호출 그래프에서 멀리 전파되지만 throw cost가 있고 ABI/binary 크기 비용도 있다.
  2. return code + out-parameter — C 스타일. Status Foo(Bar* out). 호출 코드가 장황.
  3. 결과+오류를 한 값으로std::optional, std::variant, 그리고 본격적 도구 Expected<T, E> / StatusOr<T>.

Expected는 셋째 길이다. throw cost를 피하면서 결과와 오류를 명시적으로 다룬다.

folly::Expected<int, ParseError> ParseInt(folly::StringPiece s);
auto r = ParseInt("42");
if (r) {
std::cout << *r; // 정상
} else {
std::cout << r.error(); // 오류
}

bool 변환으로 분기, * 또는 value()로 값, error()로 오류. 간단해 보이지만 monadic 조합에서 진가가 나온다.

#Monadic 흐름 — 그림

Expected의 진가는 chain에서 나온다. 각 단계가 OK면 통과, ERR면 short-circuit.

Monadic Expected

.then(f) / .thenError(g) / EXPECTED_OR_RETURN 모두 이 모델의 다른 표현이다. absl::StatusOr, std::expected (C++23)도 같은 그림 위에 있다.

#API

#include <folly/Expected.h>
enum class ParseError { NotANumber, Overflow };
folly::Expected<int, ParseError> ParseInt(folly::StringPiece s) {
int v;
auto [p, ec] = std::from_chars(s.begin(), s.end(), v);
if (ec == std::errc::invalid_argument) {
return folly::makeUnexpected(ParseError::NotANumber);
}
if (ec == std::errc::result_out_of_range) {
return folly::makeUnexpected(ParseError::Overflow);
}
return v; // 암시 변환
}
void Use() {
auto r = ParseInt("42");
CHECK(r.hasValue());
CHECK_EQ(*r, 42);
auto e = ParseInt("xyz");
CHECK(e.hasError());
CHECK(e.error() == ParseError::NotANumber);
}

생성 패턴:

  • 정상값: return v; (T로부터 암시 변환) 또는 folly::Expected<T, E>{v}.
  • 오류값: return folly::makeUnexpected(e);.

접근:

  • hasValue() / hasError() / operator bool().
  • value(), operator*(), operator->() — 오류면 throw.
  • error(), tryGetExceptionObject().
  • value_or(default) — 오류면 default.

#Monadic 조합

Expected monadic chain

folly::Expected<User, Error> LookupUser(UserId id);
folly::Expected<Email, Error> GetEmail(const User& u);
folly::Expected<bool, Error> SendNotification(const Email& e);
folly::Expected<bool, Error> Pipeline(UserId id) {
return LookupUser(id)
.then([](User u) { return GetEmail(u); })
.then([](Email e) { return SendNotification(e); });
}

then(f)는 정상값이면 f를 호출하고 결과 Expected를 반환, 오류면 그대로 전파.

thenOrThrow(f), orElse(f), transform(f), transformError(f) 등 다양한 조합기.

auto result = Lookup(id)
.transform([](User u) { return u.name; }) // T → U 매핑
.transformError([](Error e) { return LogError{e}; }) // E → F 매핑
.value_or("anonymous");

monadic 체인이 자연스럽다. 표준 std::expected (C++23)도 비슷한 API를 가졌다.

#내부 구조

// folly/Expected.h 약식
template <class T, class E>
class Expected {
public:
// tagged union
union Storage {
T value_;
E error_;
};
Storage storage_;
enum class State { hasValue, hasError, empty } state_;
// 생성자/소멸자가 state에 따라 분기
~Expected() {
if (state_ == State::hasValue) storage_.value_.~T();
else if (state_ == State::hasError) storage_.error_.~E();
}
// 접근
bool hasValue() const noexcept { return state_ == State::hasValue; }
T& value() & { if (!hasValue()) throw_(); return storage_.value_; }
E& error() & { return storage_.error_; }
};

본질적으로 variant<T, E>와 비슷하지만 ExpectedT가 우선이라는 의미가 인코딩된다. *expected가 값을 의미하지 오류를 의미하지 않는다.

#빈 상태 (empty)

folly::Expected<int, Error> e; // 기본 생성자가 있다 — 빈 상태
e = ParseInt("42"); // 채워짐

표준 std::expected는 항상 value 또는 error를 보유한다. folly는 empty 상태가 추가로 있다. 기본 생성자, moved-from 상태를 표현하기 위함. 사용 전에 채워야 한다는 계약.

#std::expected (C++23)와의 비교

항목std::expectedfolly::Expected
도입C++232015 (Folly)
empty state없음 (T 또는 E)있음 (empty)
and_then있음then
transform있음transform
or_else있음orElse
swap, hash있음있음
void Texpected<void, E> 지원지원
예외bad_expected_accessBadExpectedAccess

C++23이 표준화하면서 folly와 거의 같은 모양이 됐다. fbcode는 점진적으로 표준으로 이주 중이지만 모든 컴파일러가 받지 못한 시점이라 folly 버전이 한동안 더 살아남는다.

#absl::StatusOr와의 비교

absl::StatusOr<int> a = absl::InvalidArgumentError("bad");
folly::Expected<int, Error> b = folly::makeUnexpected(Error::Bad);
// 비슷한 패턴, 다른 type system
if (a.ok()) use(*a); else log(a.status());
if (b) use(*b); else log(b.error());
항목absl::StatusOrfolly::Expected<T, E>
오류 타입absl::Status 고정E 자유
오류 메시지항상 문자열 + codeE가 정의하기 나름
monadic약함 (.value_or 정도)강함 (.then, .transform)
도메인RPC 응답에 최적일반 도메인 오류

StatusOr문자열 메시지가 항상 따라온다는 게 매력. 디버깅에 강하다. Expected오류 enum/struct를 자유롭게 선택해 분기가 쉽다.

선택 기준: cross-service RPC면 StatusOr(혹은 absl::Status), 내부 domain logic이면 Expected<T, EnumOrStruct>.

#코드 리뷰 포인트

  • *expectedbool 체크 없이 사용 → throw로 가는 길.
  • 함수 시그니처가 Expected<int, std::string>처럼 string error → enum/struct로 type-safe하게.
  • value_or(0) 패턴이 0을 의미 있는 값과 구별 못 함 → optional도 같은 함정. 분기 명시가 안전.
  • Expected를 throw 대용으로 쓰면서 호출자가 항상 무시 → throw가 더 적합한 경우가 있다.
  • monadic 체인이 길어지면 가독성 손해. 변수에 풀어쓰는 게 나을 때도 많다.

#자주 보는 안티패턴

// 1. Expected를 가져서 즉시 *
Result r = *Compute(); // 오류 처리 안 함
// 2. Expected를 함수 시그니처로 받음 (input이 아니라)
void Process(folly::Expected<int, Error> x); // 이상하다. 호출자가 미리 분기해야 의미.
// 3. E가 trivially copyable이 아닌 무거운 타입
folly::Expected<int, std::vector<std::string>> Compute(); // 오류 path가 무겁다
// 4. 오류 enum이 success도 표현
enum class Status { Ok, Error };
folly::Expected<int, Status> ParseInt(...);
// → Ok가 의미 없음. Expected에는 오류 only enum이 옳다.

#정리

  • Expected<T, E>는 정상값 또는 오류를 한 타입에 표현하는 sum type이다.
  • monadic then/transform/orElse로 조합한다.
  • std::expected(C++23)와 거의 같은 모양 — folly가 선구자.
  • absl::StatusOr<T>는 오류 타입이 Status 고정, 메시지 풍부. RPC 도메인.
  • empty 상태가 있다는 점만 표준과 다르다 — moved-from 표현용.

#다음 편

Part 16-02: folly::Try에서 Future 결과 wrapper를 본다.

#관련 항목

Folly Code Review · 70 of 89

  1. 1 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
  2. 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
  3. 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
  4. 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
  5. 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
  6. 6 Folly production validation 문화 — peta-scale에서 단련된 코드
  7. 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
  8. 8 folly::Promise·makeFuture — Future를 만드는 두 길
  9. 9 folly::SemiFuture vs Future — executor binding의 명시화
  10. 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
  11. 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
  12. 12 folly::Future retry·window·via — 제어 흐름 조합자
  13. 13 folly::fibers 분석 — M:N stackful coroutine
  14. 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
  15. 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
  16. 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
  17. 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
  18. 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
  19. 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
  20. 20 folly::IOBufQueue — chain의 push/pull 추상화
  21. 21 folly::io::Cursor·RWCursor — chain 위의 stream
  22. 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
  23. 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
  24. 24 folly::FBString 분석 — SSO + COW 구현
  25. 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
  26. 26 folly::StringPiece — string_view 호환 분석
  27. 27 folly Join·Split utilities — 문자열 분해와 결합
  28. 28 folly::to·tryTo — text↔num 변환 분석
  29. 29 folly Conv Customization — 사용자 타입 지원
  30. 30 folly Conv 성능 비교 — sprintf·stringstream 대비
  31. 31 folly::F14ValueMap vs std::unordered_map
  32. 32 folly::F14NodeMap — stable pointer가 필요할 때
  33. 33 folly::F14VectorMap — cache-friendly iteration
  34. 34 folly::F14FastMap — auto-select 동작
  35. 35 folly F14 internals — SIMD probing 메커니즘
  36. 36 folly::small_vector — inline storage 분석
  37. 37 folly::FixedString — compile-time string
  38. 38 folly::AtomicHashMap — lock-free read 분석
  39. 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
  40. 40 folly::EvictingCacheMap — LRU 구현 분석
  41. 41 folly::Synchronized — lock wrapper 패턴
  42. 42 folly::SharedMutex 분석
  43. 43 folly::Baton — one-shot wait 동기화
  44. 44 folly::RWSpinLock 분석
  45. 45 folly::PicoSpinLock — 1-byte spinlock
  46. 46 folly::ProducerConsumerQueue — SPSC 큐 분석
  47. 47 folly::MPMCQueue — multi-producer multi-consumer
  48. 48 folly::UnboundedQueue — 동적 크기 lock-free
  49. 49 folly::fibers::Channel — Go-like channel
  50. 50 folly::dynamic — JSON-like dynamic type 분석
  51. 51 folly JSON conversion — toJson·parseJson
  52. 52 folly dynamic ↔ struct — manual marshaling
  53. 53 folly dynamic Visitor pattern — type별 분기
  54. 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
  55. 55 folly::SingletonVault 분석 — 등록·소멸·의존성
  56. 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
  57. 57 folly::ExceptionWrapper — type-erased exception holder
  58. 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
  59. 59 folly::Optional vs std::optional
  60. 60 folly::Function vs std::function
  61. 61 folly::Lazy — 지연 초기화 wrapper
  62. 62 folly Meta 스타일 code review 패턴
  63. 63 folly anti-patterns — 잘못 쓰면 std보다 느림
  64. 64 folly vs std 선택 기준 분석
  65. 65 folly::coro 개요 — production C++20 코루틴 어댑터
  66. 66 folly::coro::Task — lazy single-shot 코루틴
  67. 67 folly::coro::AsyncGenerator — 비동기 스트림
  68. 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
  69. 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
  70. 70 folly::Expected — 결과 또는 오류
  71. 71 folly::Try — Future 결과 wrapper
  72. 72 folly::Try vs Expected 선택 기준
  73. 73 folly::Range — 일반 iterator pair
  74. 74 folly::Uri — URL 파서
  75. 75 folly Fingerprint64·128 — 분산 hash
  76. 76 folly SpookyHashV2 — fast non-crypto hash
  77. 77 folly::Init — main() 부트스트랩
  78. 78 folly::Indestructible — global lifetime 패턴
  79. 79 folly::MicroLock — 1-byte 락
  80. 80 folly::MicroSpinLock — 가장 좁은 spin lock
  81. 81 folly::format — legacy formatter 분석
  82. 82 folly::demangle — typeid 디망글링
  83. 83 folly::DynamicConverter — dynamic ↔ struct
  84. 84 folly::RecordIO — append-only 로그 파일 포맷
  85. 85 folly::io::Compression — zstd·lz4·snappy wrapper
  86. 86 folly::AsyncIO — io_uring·Linux AIO
  87. 87 folly::CancellationToken — 코루틴·Future 취소 전파
  88. 88 folly::observer — hot config의 atomic refresh
  89. 89 fbcode 패턴 모음 — folly 사용의 실전