Skip to content

pimalaya/io-socket

I/O Socket Documentation Matrix Mastodon

I/O-free socket client library written in Rust

This library provides an I/O-agnostic abstraction over sockets — both stream sockets (TCP, Unix) and datagram sockets (UDP) — based on three concepts:

Coroutine

A coroutine is an I/O-free, resumable and composable state machine. It emits I/O requests without performing any I/O itself, and receives I/O responses to make progress. A coroutine is terminated when it stops emitting I/O requests.

See available coroutines at ./src/coroutines.

Runtime

A runtime contains all the I/O logic. It is responsible for processing I/O requests and returning the corresponding I/O responses. A runtime targets a specific transport and execution model (blocking std, async Tokio, UDP…).

See available runtimes at ./src/runtimes.

Loop

The loop is the glue between coroutines and runtimes. It drives the coroutine forward by feeding each SocketOutput back as the next argument, until the coroutine terminates.

Examples

Read from a TCP stream (blocking)

use std::net::TcpStream;

use io_socket::{
    coroutines::read::{ReadSocket, ReadSocketResult},
    runtimes::std_stream::handle,
};

let mut stream = TcpStream::connect("example.com:80").unwrap();

let mut arg = None;
let mut read = ReadSocket::new();

let (buf, n) = loop {
    match read.resume(arg.take()) {
        ReadSocketResult::Ok { buf, n } => break (buf, n),
        ReadSocketResult::Io { input } => arg = Some(handle(&mut stream, input).unwrap()),
        ReadSocketResult::Eof => break (vec![], 0),
        ReadSocketResult::Err { err } => panic!("{err}"),
    }
};

let bytes = &buf[..n];

Write on a TCP stream (async)

use tokio::net::TcpStream;

use io_socket::{
    coroutines::write::{WriteSocket, WriteSocketResult},
    runtimes::tokio_stream::handle,
};

let mut stream = TcpStream::connect("example.com:80").await.unwrap();

let mut arg = None;
let mut write = WriteSocket::new(b"GET / HTTP/1.0\r\n\r\n".to_vec());

loop {
    match write.resume(arg.take()) {
        WriteSocketResult::Ok { .. } => break,
        WriteSocketResult::Io { input } => arg = Some(handle(&mut stream, input).await.unwrap()),
        WriteSocketResult::Eof => panic!("connection closed"),
        WriteSocketResult::Err { err } => panic!("{err}"),
    }
}

See complete examples at ./examples.

More examples

Have a look at projects built on the top of this library:

  • io-dns: I/O-free DNS client library
  • io-addressbook: Set of I/O-free coroutines to manage contacts
  • io-http: Set of I/O-free Rust coroutines to manage HTTP sockets
  • io-oauth: Set of I/O-free Rust coroutines to manage OAuth flows
  • io-starttls: I/O-free coroutine to upgrade any plain socket to a secure one
  • io-timer: Set of I/O-free coroutines to manage timers
  • Cardamum: CLI to manage contacts
  • Comodoro: CLI to manage timers
  • Ortie: CLI to manage OAuth access tokens

License

This project is licensed under either of:

at your option.

Social

Sponsoring

nlnet

Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:

If you appreciate the project, feel free to donate using one of the following providers:

GitHub Ko-fi Buy Me a Coffee Liberapay thanks.dev PayPal