libc
import std::ffi::libc; · source
The libc surface
// Declarations are grouped by the header they come from, one extern block per group:
extern "C" {
function kill(pid: i32, sig: i32) -> i32;
function raise(sig: i32) -> i32;
function sigaction(sig: i32, act: u8*, oldact: u8*) -> i32;
function sigprocmask(how: SigprocmaskHow, set: u8*, oldset: u8*) -> i32;
// ...
}
// Closed sets are typed enums, variant names verbatim from the header:
type enum Whence : i32 {
SEEK_SET = 0;
SEEK_CUR = 1;
SEEK_END = 2;
SEEK_DATA = 3;
SEEK_HOLE = 4;
}
// Bit-flag families and error codes are plain constants:
const O_RDONLY: i32 = 0;
const O_WRONLY: i32 = 1;
const O_RDWR: i32 = 2;
const EINTR: i32 = 4;
// Layout-stable structs are real Cryo types:
type struct timespec {
tv_sec: i64;
tv_nsec: i64;
}
function errno() -> i32;
A single home for every extern "C" the standard library needs — some 640 functions — plus broad coverage of the POSIX and glibc surface for downstream use. Modules call libc::read(...), libc::sin(...) and so on, which keeps the rest of the tree free of inline extern blocks.
Functions mirror the libc and POSIX headers verbatim — same names, same signatures, same errno semantics. Idiomatic wrappers that return Result, work in Slice<u8>, or retry on EINTR live at the call site, not here. io::fd is the canonical example.
Sections are grouped by header, so you can skim for a function the way you would in man 3 or /usr/include:
errno.h signal.h fcntl.h sys/stat.h sys/uio.h unistd.h
stdio.h stdlib.h string.h ctype.h time.h sys/wait.h
sys/socket.h arpa/inet.h netdb.h sys/select.h poll.h sys/mman.h
sys/epoll.h sys/eventfd.h sys/signalfd.h dirent.h pthread.h termios.h
sys/ioctl.h locale.h pwd.h grp.h regex.h math.h
dlfcn.h sys/resource.h
Where glibc and msvcrt disagree — errno values above 34, the _O_* open flags, the errno accessor itself — the declarations are gated per target with ![target(unix)] / ![target(windows)], so one libc:: spelling resolves to the right symbol on each.
Typed enums and flag constants
The split is deliberate and worth knowing before you go looking for a name.
Closed sets of mutually exclusive values are type enum Name : i32, so they pass to libc as typed parameters and the compiler catches a wrong-family argument. Variant names are kept verbatim from the C header — ClockId::CLOCK_MONOTONIC, not ClockId::Monotonic — so call sites read like the man pages.
type enum Whence : i32 {
SEEK_SET = 0;
SEEK_CUR = 1;
SEEK_END = 2;
SEEK_DATA = 3;
SEEK_HOLE = 4;
}
type enum ClockId : i32 {
CLOCK_REALTIME = 0;
CLOCK_MONOTONIC = 1;
CLOCK_PROCESS_CPUTIME_ID = 2;
CLOCK_THREAD_CPUTIME_ID = 3;
CLOCK_MONOTONIC_RAW = 4;
CLOCK_REALTIME_COARSE = 5;
CLOCK_MONOTONIC_COARSE = 6;
CLOCK_BOOTTIME = 7;
CLOCK_REALTIME_ALARM = 8;
CLOCK_BOOTTIME_ALARM = 9;
CLOCK_TAI = 11;
}
type enum AddressFamily : i32 {
AF_UNSPEC = 0;
AF_UNIX = 1;
AF_INET = 2;
AF_AX25 = 3;
AF_IPX = 4;
AF_APPLETALK = 5;
AF_NETROM = 6;
AF_BRIDGE = 7;
AF_ATMPVC = 8;
AF_X25 = 9;
AF_INET6 = 10;
AF_NETLINK = 16;
AF_PACKET = 17;
AF_BLUETOOTH = 31;
}
type enum Protocol : i32 {
IPPROTO_IP = 0;
IPPROTO_ICMP = 1;
IPPROTO_IGMP = 2;
IPPROTO_TCP = 6;
IPPROTO_UDP = 17;
IPPROTO_IPV6 = 41;
IPPROTO_GRE = 47;
IPPROTO_ESP = 50;
IPPROTO_AH = 51;
IPPROTO_ICMPV6 = 58;
IPPROTO_RAW = 255;
}
type enum ShutdownHow : i32 {
SHUT_RD = 0;
SHUT_WR = 1;
SHUT_RDWR = 2;
}
type enum DirentType : u8 {
DT_UNKNOWN = 0;
DT_FIFO = 1;
DT_CHR = 2;
DT_DIR = 4;
DT_BLK = 6;
DT_REG = 8;
DT_LNK = 10;
DT_SOCK = 12;
DT_WHT = 14;
}
The full set: Whence, ClockId, ItimerWhich, AddressFamily, Protocol, SocketLevel, ShutdownHow, EpollOp, DirentType, MutexType, DetachState, CancelState, CancelType, LockType, FadviseAdvice, MadviseAdvice, BufferingMode, SigprocmaskHow, LocaleCategory, Resource, RusageWho, PriorityWhich, and the termios TcAction / TcQueue / TcFlow.
Bit-flag families stay as plain consts, because they get OR'd together and compared against return values, which a closed enum does not fit: O_*, S_IF*, POLL*, MAP_*, EPOLL*, AI_*, signal numbers, and errno codes.
C structs
Layout-stable POSIX structs are defined here as Cryo types with matching field order, so you can construct them directly:
type struct timespec {
tv_sec: i64;
tv_nsec: i64;
}
type struct timeval {
tv_sec: i64;
tv_usec: i64;
}
type struct tm {
tm_sec: i32;
tm_min: i32;
tm_hour: i32;
tm_mday: i32;
tm_mon: i32;
tm_year: i32;
tm_wday: i32;
tm_yday: i32;
tm_isdst: i32;
tm_gmtoff: i64;
tm_zone: u8*;
}
type struct iovec {
iov_base: u8*;
iov_len: u64;
}
type struct pollfd {
fd: i32;
events: i16;
revents: i16;
}
type struct in_addr {
s_addr: u32;
}
type struct in6_addr {
s6_addr: u8[16];
}
type struct rlimit {
rlim_cur: u64;
rlim_max: u64;
}
type struct winsize {
ws_row: u16;
ws_col: u16;
ws_xpixel: u16;
ws_ypixel: u16;
}
type struct regmatch_t {
rm_so: i64;
rm_eo: i64;
}
Platform-variant structs are treated as opaque byte buffers that you pack and unpack yourself — sockaddr_in, struct stat, addrinfo, epoll_event, struct termios, and the pthread_* family. The accompanying SIZEOF_* constants give the Linux x86-64 size so a u8[SIZEOF_X] buffer comes out the right length. net::addr::sockaddr does exactly this for sockaddr_in.
Errno, signal, mode, fcntl, epoll, and ioctl values are the Linux x86-64 / glibc ABI. Other platforms re-derive them from the corresponding headers.
Floating-point classification — isnan, isinf, finite, signbit, fpclassify — is deliberately not declared here. It is implemented in pure Cryo over the IEEE-754 bit pattern instead, since those are macros rather than exported symbols in most libcs.