Skip to content
CryoCryo home
Stdlibnet::socket

tcp

import std::net::socket::tcp; · source

TcpListener

type struct TcpListener {
    fd: i32;

    static bind(local: SocketAddr) -> Result<TcpListener, IoError>;
    static from_fd(sock: i32) -> TcpListener;
    raw_fd(&this) -> i32;
    set_nonblocking(mut &this, on: boolean) -> Result<(), IoError>;
    local_addr(&this) -> Result<SocketAddr, IoError>;
    drop(mut &this) -> void;
}

TcpListener::bind is the one plain call in this file — binding does not wait. Everything else is a future with a start(...) constructor:

import std::net::socket::tcp;
import std::net::addr::socket_addr;
import std::io::buf;

async function serve(addr: SocketAddr) -> Result<(), IoError> {
    mut listener: TcpListener = TcpListener::bind(addr)?;
    loop {
        mut accepted: TcpAccepted = await TcpAccept::start(listener);
        listener = accepted.take_listener();
        mut conn: BufStream<TcpStream> = BufStream<TcpStream>::of(accepted.take_result()?);
        await handle(conn);
    }
}

TcpAccept puts the sockets it produces in non-blocking mode, and the listener closes its descriptor through an inherent drop.

TcpStream

type struct TcpStream {
    fd: i32;

    static from_fd(sock: i32) -> TcpStream;
    raw_fd(&this) -> i32;
    shutdown_write(mut &this) -> Result<(), IoError>;
    set_nonblocking(mut &this, on: boolean) -> Result<(), IoError>;
    drop(mut &this) -> void;
}

A connected socket. You get one from TcpConnect or TcpAccept, both of which leave it in non-blocking mode; a stream adopted with from_fd needs set_nonblocking(true) before it is used with a future. shutdown_write half-closes so the peer sees EOF. It closes its descriptor through an inherent drop.

The futures

Every operation that can wait is one of these four, each with a start(...) constructor. Each attempts its syscall on the non-blocking socket, and if the socket is not ready registers the polling task's waker with the current Reactor and returns Pending. Registering after the failed attempt is what makes the race safe: the reactor arms level-triggered, so readiness that arrived in between is still reported. These futures park only where a reactor exists — an Executor's worker threads — and polling one anywhere else panics rather than parking a task that could never be woken.

Dropping a future mid-operation releases its reactor registration and, for TcpConnect, closes the half-connected socket — see cancellation is a drop. Only the direction that future owned is cancelled, so a write parked on the same socket keeps waiting.

TcpConnect

type struct TcpConnect {
    fd:      i32;
    addr:    u8[28];
    addrlen: u32;
    err:     IoError;
    rt:      Reactor*;

    static start(remote: SocketAddr) -> TcpConnect;
}

Completes with Result<TcpStream, IoError>. The socket is opened and the handshake started at construction; each poll asks the OS whether it finished, parking on writability in between.

TcpAccept

type struct TcpAccept {
    listener: TcpListener;
    rt:       Reactor*;

    static start(listener: TcpListener) -> TcpAccept;
}

Completes with a TcpAccepted: the listener back, plus the new stream or the error.

TcpRead

type struct TcpRead {
    stream: TcpStream;
    buf:    Array<u8>;
    rt:     Reactor*;

    static start(stream: TcpStream, buf: Array<u8>) -> TcpRead;
}

Completes with a TcpIo: the stream and buffer back, with outcome() the byte count — 0 at EOF. A read fills buf's [0, length) and truncates it to what arrived, so hand in one sized with resize.

TcpWrite

type struct TcpWrite {
    stream: TcpStream;
    buf:    Array<u8>;
    rt:     Reactor*;

    static start(stream: TcpStream, buf: Array<u8>) -> TcpWrite;
}

Completes with a TcpIo whose outcome() is the bytes accepted, which may be fewer than offered — the caller sends the remainder in another operation.

The outcomes

type struct TcpIo {
    stream: TcpStream;
    buf:    Array<u8>;
    result: Result<u64, IoError>;

    take_stream(mut &this) -> TcpStream;
    take_buf(mut &this) -> Array<u8>;
    outcome(&this) -> Result<u64, IoError>;
}
type struct TcpAccepted {
    listener: TcpListener;
    result:   Result<TcpStream, IoError>;

    take_listener(mut &this) -> TcpListener;
    take_result(mut &this) -> Result<TcpStream, IoError>;
}

The outputs hand their handles back through take_* rather than by field: a TcpStream closes its descriptor when dropped, so the compiler will not let a field be moved out of an outcome that would still drop it. take_stream swaps in a closed placeholder, leaving nothing behind to close. outcome() is how many bytes moved, or why none did.

Trait implementations

implement trait AsyncTransport for struct TcpStream

implement trait Future for struct TcpConnect

implement trait Drop for struct TcpConnect

implement trait Future for struct TcpAccept

implement trait Drop for struct TcpAccept

implement trait Future for struct TcpRead

implement trait Drop for struct TcpRead

implement trait Future for struct TcpWrite

implement trait Drop for struct TcpWrite

AsyncTransport is what lets BufStream<TcpStream> exist — its read_into and write_from are TcpRead and TcpWrite with the move-out/move-back handled inside.