folly::Expected — 결과 또는 오류
한 줄 요약:
Expected<T, E>는 함수의 정상 결과 또는 오류 코드를 하나의 값으로 표현한다. C++23std::expected의 선구자이자absl::StatusOr<T>의 사촌으로 monadic 조합이 가능한 모델이다.
#동기
C++의 오류 표현은 셋이다.
- 예외(throw) — 호출 그래프에서 멀리 전파되지만 throw cost가 있고 ABI/binary 크기 비용도 있다.
- return code + out-parameter — C 스타일.
Status Foo(Bar* out). 호출 코드가 장황. - 결과+오류를 한 값으로 —
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.
.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 조합
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>와 비슷하지만 Expected는 T가 우선이라는 의미가 인코딩된다. *expected가 값을 의미하지 오류를 의미하지 않는다.
#빈 상태 (empty)
folly::Expected<int, Error> e; // 기본 생성자가 있다 — 빈 상태e = ParseInt("42"); // 채워짐표준 std::expected는 항상 value 또는 error를 보유한다. folly는 empty 상태가 추가로 있다. 기본 생성자, moved-from 상태를 표현하기 위함. 사용 전에 채워야 한다는 계약.
#std::expected (C++23)와의 비교
| 항목 | std::expected | folly::Expected |
|---|---|---|
| 도입 | C++23 | 2015 (Folly) |
| empty state | 없음 (T 또는 E) | 있음 (empty) |
and_then | 있음 | then |
transform | 있음 | transform |
or_else | 있음 | orElse |
| swap, hash | 있음 | 있음 |
| void T | expected<void, E> 지원 | 지원 |
| 예외 | bad_expected_access | BadExpectedAccess |
C++23이 표준화하면서 folly와 거의 같은 모양이 됐다. fbcode는 점진적으로 표준으로 이주 중이지만 모든 컴파일러가 받지 못한 시점이라 folly 버전이 한동안 더 살아남는다.
#absl::StatusOr와의 비교
absl::StatusOr<int> a = absl::InvalidArgumentError("bad");folly::Expected<int, Error> b = folly::makeUnexpected(Error::Bad);
// 비슷한 패턴, 다른 type systemif (a.ok()) use(*a); else log(a.status());if (b) use(*b); else log(b.error());| 항목 | absl::StatusOr | folly::Expected<T, E> |
|---|---|---|
| 오류 타입 | absl::Status 고정 | E 자유 |
| 오류 메시지 | 항상 문자열 + code | E가 정의하기 나름 |
| monadic | 약함 (.value_or 정도) | 강함 (.then, .transform) |
| 도메인 | RPC 응답에 최적 | 일반 도메인 오류 |
StatusOr는 문자열 메시지가 항상 따라온다는 게 매력. 디버깅에 강하다. Expected는 오류 enum/struct를 자유롭게 선택해 분기가 쉽다.
선택 기준: cross-service RPC면 StatusOr(혹은 absl::Status), 내부 domain logic이면 Expected<T, EnumOrStruct>.
#코드 리뷰 포인트
*expected를bool체크 없이 사용 → 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 Folly Code Review — Meta의 production-grade C++ 라이브러리 코드 분석
- 2 Folly 개요 — Meta가 production에서 검증한 utility 모음 분석
- 3 Folly vs Abseil 철학 비교 — performance-first vs std-compatible
- 4 Folly 빌드와 fbcode 환경 — monorepo의 그림자
- 5 Folly API stability 정책 — 어떤 보장도 없다는 솔직함
- 6 Folly production validation 문화 — peta-scale에서 단련된 코드
- 7 folly::Future 분석 — std::future의 한계를 넘는 composable async
- 8 folly::Promise·makeFuture — Future를 만드는 두 길
- 9 folly::SemiFuture vs Future — executor binding의 명시화
- 10 folly::Future thenValue·thenError·thenTry — continuation 체인 분석
- 11 folly::collect·collectAll·collectAny — fan-in 패턴 분석
- 12 folly::Future retry·window·via — 제어 흐름 조합자
- 13 folly::fibers 분석 — M:N stackful coroutine
- 14 folly::InlineExecutor — 호출자 thread에서 즉시 실행
- 15 folly::CPUThreadPoolExecutor — CPU-bound 작업의 표준 thread pool
- 16 folly::IOThreadPoolExecutor — libevent 기반 I/O pool
- 17 folly::ManualExecutor — 결정적 테스트를 위한 수동 진행
- 18 folly::EventBase 분석 — libevent 이벤트 루프의 핵심
- 19 folly::IOBuf 분석 — zero-copy buffer chain의 기본 단위
- 20 folly::IOBufQueue — chain의 push/pull 추상화
- 21 folly::io::Cursor·RWCursor — chain 위의 stream
- 22 folly Zero-copy 패턴 — IOBuf로 ScatterGather I/O 표현
- 23 folly::IOBuf shared semantics — clone·unshare·takeOwnership
- 24 folly::FBString 분석 — SSO + COW 구현
- 25 folly의 fmt::format 통합 — 모던 포맷팅 채택
- 26 folly::StringPiece — string_view 호환 분석
- 27 folly Join·Split utilities — 문자열 분해와 결합
- 28 folly::to·tryTo — text↔num 변환 분석
- 29 folly Conv Customization — 사용자 타입 지원
- 30 folly Conv 성능 비교 — sprintf·stringstream 대비
- 31 folly::F14ValueMap vs std::unordered_map
- 32 folly::F14NodeMap — stable pointer가 필요할 때
- 33 folly::F14VectorMap — cache-friendly iteration
- 34 folly::F14FastMap — auto-select 동작
- 35 folly F14 internals — SIMD probing 메커니즘
- 36 folly::small_vector — inline storage 분석
- 37 folly::FixedString — compile-time string
- 38 folly::AtomicHashMap — lock-free read 분석
- 39 folly::ConcurrentHashMap — sharded 동시 해시 맵
- 40 folly::EvictingCacheMap — LRU 구현 분석
- 41 folly::Synchronized — lock wrapper 패턴
- 42 folly::SharedMutex 분석
- 43 folly::Baton — one-shot wait 동기화
- 44 folly::RWSpinLock 분석
- 45 folly::PicoSpinLock — 1-byte spinlock
- 46 folly::ProducerConsumerQueue — SPSC 큐 분석
- 47 folly::MPMCQueue — multi-producer multi-consumer
- 48 folly::UnboundedQueue — 동적 크기 lock-free
- 49 folly::fibers::Channel — Go-like channel
- 50 folly::dynamic — JSON-like dynamic type 분석
- 51 folly JSON conversion — toJson·parseJson
- 52 folly dynamic ↔ struct — manual marshaling
- 53 folly dynamic Visitor pattern — type별 분기
- 54 folly::Singleton vs Meyers/static — 왜 Folly의 Singleton인가
- 55 folly::SingletonVault 분석 — 등록·소멸·의존성
- 56 folly::Singleton try_get·try_get_fast — TLS-cached 접근
- 57 folly::ExceptionWrapper — type-erased exception holder
- 58 folly::ScopeGuard·SCOPE_EXIT — RAII cleanup
- 59 folly::Optional vs std::optional
- 60 folly::Function vs std::function
- 61 folly::Lazy — 지연 초기화 wrapper
- 62 folly Meta 스타일 code review 패턴
- 63 folly anti-patterns — 잘못 쓰면 std보다 느림
- 64 folly vs std 선택 기준 분석
- 65 folly::coro 개요 — production C++20 코루틴 어댑터
- 66 folly::coro::Task — lazy single-shot 코루틴
- 67 folly::coro::AsyncGenerator — 비동기 스트림
- 68 folly coro blockingWait·collectAll — 동기 경계와 fan-in
- 69 folly::coro::Baton·Mutex — 코루틴-aware 동기화
- 70 folly::Expected — 결과 또는 오류
- 71 folly::Try — Future 결과 wrapper
- 72 folly::Try vs Expected 선택 기준
- 73 folly::Range — 일반 iterator pair
- 74 folly::Uri — URL 파서
- 75 folly Fingerprint64·128 — 분산 hash
- 76 folly SpookyHashV2 — fast non-crypto hash
- 77 folly::Init — main() 부트스트랩
- 78 folly::Indestructible — global lifetime 패턴
- 79 folly::MicroLock — 1-byte 락
- 80 folly::MicroSpinLock — 가장 좁은 spin lock
- 81 folly::format — legacy formatter 분석
- 82 folly::demangle — typeid 디망글링
- 83 folly::DynamicConverter — dynamic ↔ struct
- 84 folly::RecordIO — append-only 로그 파일 포맷
- 85 folly::io::Compression — zstd·lz4·snappy wrapper
- 86 folly::AsyncIO — io_uring·Linux AIO
- 87 folly::CancellationToken — 코루틴·Future 취소 전파
- 88 folly::observer — hot config의 atomic refresh
- 89 fbcode 패턴 모음 — folly 사용의 실전
관련 글
folly::Try vs Expected 선택 기준
언제 Try, 언제 Expected — 비동기 결과 슬롯과 도메인 오류 표현의 명확한 분리.
같은 시리즈에서 이어 읽기
folly::Try — Future 결과 wrapper
Try<T>의 세 상태 (value/exception/empty), Future 내부에서의 역할, exception_wrapper와의 관계.
같은 시리즈에서 이어 읽기
fbcode 패턴 모음 — folly 사용의 실전
Meta fbcode 코드 리뷰에서 반복적으로 등장하는 folly 사용 패턴 — overview + 시리즈 마무리.
같은 시리즈에서 이어 읽기