Exception-safe IO building blocks written in modern C++23 modules.
Working with raw file descriptors and POSIX APIs usually means manual error
handling. The jowi.io modules wrap those primitives with helpers that return
std::expected, enforce RAII semantics, and provide buffered reader utilities.
Most exported functions are marked noexcept so failures are funnelled through
std::expected, keeping control flow predictable.
- Compiler: A C++23-capable toolchain with module support (
importis used throughout the library). - Standard library: Requires
<expected>,<format>, and other C++23 facilities.
std::expectedeverywhere: Operations that can fail returnstd::expected<T, IoError>, allowing clean monadic chaining.noexceptdefault: Most member functions are annotatednoexcept, making it safe to rely on unwinding-free error paths.- RAII-first: Owning wrappers clean up file descriptors automatically via injected closer functors.
-
jowi.io- Umbrella module that re-exports all IO submodules so downstream code can
simply
import jowi.io;.
- Umbrella module that re-exports all IO submodules so downstream code can
simply
-
jowi.io:buffer- Concepts
IsReadableBuffer,IsWritableBuffer, andIsRwBufferare used to constrain templated IO helpers. DynBuffer(member highlights):read_buf(),read_beg(),read_end(),read_size(),is_full()– allnoexceptviews of the readable region.write_beg(),writable_size(),finish_write(),reset(),resize()– manage the writable region; shrinking/growth never throws.
FixedBuffer<N>mirrors the same API using a compile-time capacity.
- Concepts
-
jowi.io:errorIoErrorextendsstd::exception, captureserrno, and formats messages viastd::format. UseIoError::str_error(errno)to translate system errors.
-
jowi.io:file_descriptorFileHandle::fd()andFileHandle::borrow()expose the descriptor value without taking ownership.FileDescriptorsupports move construction/assignment, automatically calling the closer in itsnoexceptdestructor.IsFileDescriptorconcept checks compatibility with generic utilities.
-
jowi.io:fd_type- Defines the POSIX closer functor and aliases
FileType(owning) andFileHandleType(non-owning) for use across the library.
- Defines the POSIX closer functor and aliases
-
jowi.io:is_file- Concepts (
IsReadable,IsWritable,IsSeekable,IsSyncable,IsSized,IsFile) encapsulate the capability contracts used across the library. BasicFileexposeshandle()andinvalid_file()for lightweight descriptor passing.
- Concepts (
-
jowi.io:readersByteReadercombines a buffer and readable file, providing:read_buf()/next_buf()for buffer management.read_n(n),read(),read_until(pred), andread_until(char)returningstd::expected<std::string, IoError>.release()to transfer ownership of the underlying file.
LineReaderbuilds onByteReaderwithread_line()andread_lines().CsvReaderoffersread_row()(optional row) andread_rows()for bulk ingestion.
-
jowi.io:local_fileLocalFilemember highlights (allnoexceptunless returningstd::expected):write(std::string_view)andread(buffer).seek(SeekMode, off_t),seek_beg/seek_cur/seek_end,size(),truncate(off_t),sync().handle()returns a borrowed descriptor for integration with other APIs.
OpenOptionsprovides fluent toggles:read(),write(),read_write(),truncate(),append(),create(), thenopen(path).
-
jowi.io:pipeopen_pipe(non_blocking)yields{ReaderPipe, WriterPipe}.ReaderPipe::read(buffer)andWriterPipe::write(view)forward tosys_read/sys_writewhileis_readable()/is_writable()wrapsys_file_poller.
-
jowi.io:in_mem_fileInMemFileis a growable, vector-backed file object that satisfies the readable/writable/seekable portions ofIsFile, making it ideal for tests or buffering data entirely in memory.
-
jowi.io:sys_utilsys_call(func, args...)returnsexpected<decltype(func(args...)), IoError>.sys_call_void(func, args...)discards success values while preservingIoErrorreporting.
-
jowi.io:sys_file- Wrappers:
sys_seek,sys_truncate,sys_write,sys_sync,sys_read– all returnstd::expectedand leave buffers consistent on failure. - Utilities:
sys_file_poller::read_poller()/write_poller()andsys_fcntl,sys_fcntl_or_flagfor descriptor flags.
- Wrappers:
-
jowi.io:sys_net- Address helpers:
Ipv4Address::addr(),port(),all_interface(),localhost(),sys_addr(),sys_domain();LocalAddress::addr()etc. SockRwprovidesread(buffer),write(view),is_readable(),is_writable(), andhandle()for connected descriptors.TcpSocket<addr>offerscreate(addr),connect(), andlisten(backlog).TcpListener<addr>exposesaccept(),async_accept(),is_readable(), andhandle().UdpSocket<addr>providescreate(addr),bind(), andconnect()helpers.
- Address helpers:
The modules are designed to compose: start from jowi.io for a single import,
then opt into specific concepts or concrete types. Buffer implementations work
hand-in-hand with the reader utilities, while the Unix back-end helpers
translate POSIX errors into the shared IoError type.
import jowi.io;
import std;
using namespace jowi::io;
int main() {
auto file = OpenOptions{}.read().open("/var/log/system.log");
if (!file) return 1;
auto reader = ByteReader{DynBuffer{4096}, std::move(*file)};
auto contents = reader.read();
if (!contents) return 1;
std::println("{} bytes read", contents->size());
return 0;
}import jowi.io;
import std;
using namespace jowi::io;
int main() {
auto pipe_pair = open_pipe();
if (!pipe_pair) return 1;
auto &[reader, writer] = *pipe_pair;
auto write_res = writer.write("hello\n");
if (!write_res) return 1;
DynBuffer buf{32};
if (auto ready = reader.is_readable(); !ready || !*ready) return 1;
if (auto read_res = reader.read(buf); !read_res) return 1;
std::string_view payload{buf.read_beg(), buf.read_end()};
std::println("pipe read: {}", payload);
return 0;
}import jowi.io;
import std;
using namespace jowi::io;
int main() {
auto addr = Ipv4Address{}.all_interface().port(8080);
auto tcp = Ipv4TcpSocket::create(addr);
if (!tcp) return 1;
auto listener = std::move(*tcp).listen(64);
if (!listener) return 1;
auto next = listener->accept();
if (!next) return 1;
auto &[client_addr, connection] = *next;
(void)client_addr;
(void)connection;
return 0;
}