[libc-commits] [libc] [libc] Implement expected<void, E> and use it in internal utilities (PR #215020)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Sat Aug 8 13:16:13 PDT 2026
https://github.com/labath created https://github.com/llvm/llvm-project/pull/215020
Add the expected<void, E> partial template specialization to support functions that return an error on failure and nothing on success, matching std::expected<void, E>.
I've used this to resolve a TODO in if_nameindex and converted a few other simple internal functions (pwd_utils::open/close, update_from_seconds, stat_via_statx).
Assisted by Gemini.
>From 70b67c8334b2feb7dd31fed10ae02c2d88799cb6 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Sat, 8 Aug 2026 20:09:47 +0000
Subject: [PATCH] [libc] Implement expected<void, E> and use it in internal
utilities
Add the expected<void, E> partial template specialization to support
functions that return an error on failure and nothing on success,
matching std::expected<void, E>.
I've used this to resolve a TODO in if_nameindex and converted a few
other simple internal functions (pwd_utils::open/close,
update_from_seconds, stat_via_statx).
Assisted by Gemini.
---
libc/src/__support/CPP/expected.h | 23 +++++++
.../OSUtil/linux/stat/stat_via_statx.h | 6 +-
libc/src/net/linux/if_nameindex_impl.h | 7 +--
libc/src/pwd/pwd_utils.cpp | 8 +--
libc/src/pwd/pwd_utils.h | 4 +-
libc/src/sys/stat/linux/fstat.cpp | 2 +-
libc/src/sys/stat/linux/lstat.cpp | 2 +-
libc/src/sys/stat/linux/stat.cpp | 2 +-
libc/src/time/time_utils.cpp | 4 +-
libc/src/time/time_utils.h | 4 +-
libc/test/src/__support/CPP/expected_test.cpp | 62 +++++++++++++++++++
11 files changed, 104 insertions(+), 20 deletions(-)
diff --git a/libc/src/__support/CPP/expected.h b/libc/src/__support/CPP/expected.h
index 4cdad71a444b3..165ac72002d35 100644
--- a/libc/src/__support/CPP/expected.h
+++ b/libc/src/__support/CPP/expected.h
@@ -60,6 +60,29 @@ template <class T, class E> class expected {
LIBC_INLINE constexpr const T *operator->() const { return &exp; }
};
+template <class E> class expected<void, E> {
+ union {
+ char dummy;
+ E unexp;
+ };
+ bool is_expected;
+
+public:
+ LIBC_INLINE constexpr expected() : dummy(), is_expected(true) {}
+ LIBC_INLINE constexpr expected(unexpected<E> unexp)
+ : unexp(unexp.error()), is_expected(false) {}
+
+ LIBC_INLINE constexpr bool has_value() const { return is_expected; }
+
+ LIBC_INLINE constexpr void value() const {}
+ LIBC_INLINE constexpr E &error() { return unexp; }
+ LIBC_INLINE constexpr const E &error() const { return unexp; }
+
+ LIBC_INLINE constexpr explicit operator bool() const { return is_expected; }
+
+ LIBC_INLINE constexpr void operator*() const {}
+};
+
} // namespace cpp
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/OSUtil/linux/stat/stat_via_statx.h b/libc/src/__support/OSUtil/linux/stat/stat_via_statx.h
index e9d78099b5918..1af534189b955 100644
--- a/libc/src/__support/OSUtil/linux/stat/stat_via_statx.h
+++ b/libc/src/__support/OSUtil/linux/stat/stat_via_statx.h
@@ -30,14 +30,14 @@ namespace LIBC_NAMESPACE_DECL {
namespace internal {
/// Populates `statbuf` via a call to the `statx` syscall.
-LIBC_INLINE ErrorOr<int> stat_via_statx(int dirfd, const char *__restrict path,
+LIBC_INLINE ErrorOr<void> stat_via_statx(int dirfd, const char *__restrict path,
int flags,
struct stat *__restrict statbuf) {
kernel_statx_buf xbuf;
ErrorOr<int> result = linux_syscalls::statx(
dirfd, path, flags, KERNEL_STATX_BASIC_STATS_MASK, &xbuf);
if (!result)
- return result;
+ return Error(result.error());
statbuf->st_dev = MKDEV(xbuf.stx_dev_major, xbuf.stx_dev_minor);
statbuf->st_ino = static_cast<decltype(statbuf->st_ino)>(xbuf.stx_ino);
@@ -57,7 +57,7 @@ LIBC_INLINE ErrorOr<int> stat_via_statx(int dirfd, const char *__restrict path,
statbuf->st_blocks =
static_cast<decltype(statbuf->st_blocks)>(xbuf.stx_blocks);
- return 0;
+ return {};
}
} // namespace internal
diff --git a/libc/src/net/linux/if_nameindex_impl.h b/libc/src/net/linux/if_nameindex_impl.h
index adbc6752f0066..2e4ce089a5672 100644
--- a/libc/src/net/linux/if_nameindex_impl.h
+++ b/libc/src/net/linux/if_nameindex_impl.h
@@ -69,8 +69,7 @@ struct InterfaceEntry {
static_assert(IF_NAMESIZE - 1 < (1u << 8));
};
-// TODO: Use ErrorOr<void> when that's a thing.
-LIBC_INLINE ErrorOr<int>
+LIBC_INLINE ErrorOr<void>
parse_netlink_messages(cpp::span<uint8_t> buf,
BlockStore<InterfaceEntry, 16> &store) {
size_t len = buf.size();
@@ -119,7 +118,7 @@ parse_netlink_messages(cpp::span<uint8_t> buf,
break;
}
}
- return 0;
+ return {};
}
LIBC_INLINE ErrorOr<struct if_nameindex *>
@@ -189,7 +188,7 @@ LIBC_INLINE ErrorOr<struct if_nameindex *> if_nameindex() {
cpp::scope_exit destroy_store(
[&store]() { BlockStore<detail::InterfaceEntry, 16>::destroy(&store); });
- if (ErrorOr<int> parse_res = detail::parse_netlink_messages(
+ if (ErrorOr<void> parse_res = detail::parse_netlink_messages(
{buf, static_cast<size_t>(*recv_res)}, store);
!parse_res.has_value())
return Error(parse_res.error());
diff --git a/libc/src/pwd/pwd_utils.cpp b/libc/src/pwd/pwd_utils.cpp
index aaf0faf37e4ea..5a0c191e38260 100644
--- a/libc/src/pwd/pwd_utils.cpp
+++ b/libc/src/pwd/pwd_utils.cpp
@@ -97,7 +97,7 @@ void TESTONLY_set_passwd_path(const char *path) {
pwd_file_path = path;
}
-ErrorOr<int> open() {
+ErrorOr<void> open() {
if (!pwd_file) {
auto result = openfile(pwd_file_path, "r");
if (!result.has_value())
@@ -108,17 +108,17 @@ ErrorOr<int> open() {
if (!result.has_value())
return Error(result.error());
}
- return 0;
+ return {};
}
-ErrorOr<int> close() {
+ErrorOr<void> close() {
if (pwd_file) {
int result = pwd_file->close();
pwd_file = nullptr;
if (result != 0)
return Error(result);
}
- return 0;
+ return {};
}
struct ReadLineResult {
diff --git a/libc/src/pwd/pwd_utils.h b/libc/src/pwd/pwd_utils.h
index 7b48b2cabc8f5..57612cd8578c2 100644
--- a/libc/src/pwd/pwd_utils.h
+++ b/libc/src/pwd/pwd_utils.h
@@ -32,10 +32,10 @@ namespace passwd {
void TESTONLY_set_passwd_path(const char *path);
// Opens or rewinds the password file.
-ErrorOr<int> open();
+ErrorOr<void> open();
// Closes the password file.
-ErrorOr<int> close();
+ErrorOr<void> close();
// Reads the next entry from the password database.
ErrorOr<struct passwd *> read_next();
diff --git a/libc/src/sys/stat/linux/fstat.cpp b/libc/src/sys/stat/linux/fstat.cpp
index 447c6ce6a5bb9..20a2f34bd8c06 100644
--- a/libc/src/sys/stat/linux/fstat.cpp
+++ b/libc/src/sys/stat/linux/fstat.cpp
@@ -18,7 +18,7 @@
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, fstat, (int fd, struct stat *statbuf)) {
- ErrorOr<int> result =
+ ErrorOr<void> result =
internal::stat_via_statx(fd, "", AT_EMPTY_PATH, statbuf);
if (!result) {
libc_errno = result.error();
diff --git a/libc/src/sys/stat/linux/lstat.cpp b/libc/src/sys/stat/linux/lstat.cpp
index 4cd8d974ef413..f2d68e3d64b10 100644
--- a/libc/src/sys/stat/linux/lstat.cpp
+++ b/libc/src/sys/stat/linux/lstat.cpp
@@ -20,7 +20,7 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, lstat,
(const char *__restrict path,
struct stat *__restrict statbuf)) {
- ErrorOr<int> result =
+ ErrorOr<void> result =
internal::stat_via_statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, statbuf);
if (!result) {
libc_errno = result.error();
diff --git a/libc/src/sys/stat/linux/stat.cpp b/libc/src/sys/stat/linux/stat.cpp
index 06d1d9de894a3..b7dc46fc9d512 100644
--- a/libc/src/sys/stat/linux/stat.cpp
+++ b/libc/src/sys/stat/linux/stat.cpp
@@ -20,7 +20,7 @@ namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, stat,
(const char *__restrict path,
struct stat *__restrict statbuf)) {
- ErrorOr<int> result = internal::stat_via_statx(AT_FDCWD, path, 0, statbuf);
+ ErrorOr<void> result = internal::stat_via_statx(AT_FDCWD, path, 0, statbuf);
if (!result) {
libc_errno = result.error();
return -1;
diff --git a/libc/src/time/time_utils.cpp b/libc/src/time/time_utils.cpp
index 1ddb6f159f8f9..beb6d3d2130bc 100644
--- a/libc/src/time/time_utils.cpp
+++ b/libc/src/time/time_utils.cpp
@@ -87,7 +87,7 @@ cpp::optional<time_t> mktime_internal(const tm *tm_out) {
// This uses the proleptic Gregorian calendar: Gregorian leap-year rules are
// extended to all dates, including those before the calendar's adoption in
// 1582.
-ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm) {
+ErrorOr<void> update_from_seconds(time_t total_seconds, tm *tm) {
// Range check for valid time_t values
constexpr time_t time_min =
INT_MIN *
@@ -206,7 +206,7 @@ ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm) {
static_cast<int>(remaining_seconds % time_constants::SECONDS_PER_MIN);
tm->tm_isdst = 0; // Daylight saving time flag (not implemented)
- return 0;
+ return {};
}
} // namespace time_utils
diff --git a/libc/src/time/time_utils.h b/libc/src/time/time_utils.h
index b6b61f1be044f..281695b53273f 100644
--- a/libc/src/time/time_utils.h
+++ b/libc/src/time/time_utils.h
@@ -51,8 +51,8 @@ constexpr int TIME_OVERFLOW = ERANGE;
///
/// \param total_seconds The number of seconds since January 1st, 1970.
/// \param tm Pointer to the tm structure to update.
-/// \return 0 on success, or error code on failure.
-ErrorOr<int> update_from_seconds(time_t total_seconds, tm *tm);
+/// \return void on success, or error code on failure.
+ErrorOr<void> update_from_seconds(time_t total_seconds, tm *tm);
LIBC_INLINE ErrorOr<char *> asctime(const tm *timeptr, char *buffer,
size_t bufferLength) {
diff --git a/libc/test/src/__support/CPP/expected_test.cpp b/libc/test/src/__support/CPP/expected_test.cpp
index 21ff1d6159ff4..f54da1448e9ef 100644
--- a/libc/test/src/__support/CPP/expected_test.cpp
+++ b/libc/test/src/__support/CPP/expected_test.cpp
@@ -84,6 +84,41 @@ TEST(LlvmLibcExpectedTest, ArrowOperator) {
ASSERT_EQ(CE->get_x(), 123);
}
+TEST(LlvmLibcExpectedTest, VoidValueConstruction) {
+ expected<void, int> e;
+ ASSERT_TRUE(e.has_value());
+ ASSERT_TRUE(static_cast<bool>(e));
+ e.value();
+ *e;
+}
+
+TEST(LlvmLibcExpectedTest, VoidErrorConstruction) {
+ expected<void, int> e(unexpected(404));
+ ASSERT_FALSE(e.has_value());
+ ASSERT_FALSE(static_cast<bool>(e));
+ ASSERT_EQ(e.error(), 404);
+}
+
+TEST(LlvmLibcExpectedTest, VoidMutation) {
+ expected<void, int> u(unexpected<int>(1));
+ ASSERT_EQ(u.error(), 1);
+ u.error() = 2;
+ ASSERT_EQ(u.error(), 2);
+}
+
+TEST(LlvmLibcExpectedTest, VoidConstAccess) {
+ const expected<void, int> CE;
+ ASSERT_TRUE(CE.has_value());
+ ASSERT_TRUE(static_cast<bool>(CE));
+ CE.value();
+ *CE;
+
+ const expected<void, int> CU(unexpected(500));
+ ASSERT_FALSE(CU.has_value());
+ ASSERT_FALSE(static_cast<bool>(CU));
+ ASSERT_EQ(CU.error(), 500);
+}
+
constexpr bool test_constexpr_value() {
expected<int, int> e(42);
if (!e.has_value() || !static_cast<bool>(e))
@@ -95,6 +130,18 @@ constexpr bool test_constexpr_value() {
static_assert(test_constexpr_value(), "expected constexpr value check failed");
+constexpr bool test_constexpr_void_value() {
+ expected<void, int> e;
+ if (!e.has_value() || !static_cast<bool>(e))
+ return false;
+ e.value();
+ *e;
+ return true;
+}
+
+static_assert(test_constexpr_void_value(),
+ "expected<void> constexpr value check failed");
+
constexpr bool test_constexpr_error() {
expected<int, int> e(unexpected<int>(99));
if (e.has_value() || static_cast<bool>(e))
@@ -106,5 +153,20 @@ constexpr bool test_constexpr_error() {
static_assert(test_constexpr_error(), "expected constexpr error check failed");
+constexpr bool test_constexpr_void_error() {
+ expected<void, int> e(unexpected<int>(99));
+ if (e.has_value() || static_cast<bool>(e))
+ return false;
+ if (e.error() != 99)
+ return false;
+ return true;
+}
+
+static_assert(test_constexpr_void_error(),
+ "expected<void> constexpr error check failed");
+
static_assert(!LIBC_NAMESPACE::cpp::is_convertible_v<expected<int, long>, bool>,
"only explicit conversions allowed");
+static_assert(
+ !LIBC_NAMESPACE::cpp::is_convertible_v<expected<void, long>, bool>,
+ "only explicit conversions allowed");
More information about the libc-commits
mailing list