Design Goals
rs-netty is not a direct port of Java Netty. Its goal is to take the useful pipeline/handler/channel model and express it in a Rust-native way.
Keep The Useful Shape
The framework keeps these concepts:
- codecs sit at pipeline boundaries and convert between bytes/datagrams and typed messages.
- inbound stages process decoded inbound messages.
- final handlers implement application semantics and write application-level responses.
- outbound stages convert handler writes into values the codec can encode.
- channel handles can write, flush, or close from outside the current handler.
- lifecycle hooks can observe server, connection, and UDP socket startup/shutdown.
Use Rust Types Instead Of Dynamic Pipeline Mutation
Java Netty’s dynamic pipeline is flexible, but many mistakes show up at runtime: handlers are in the wrong order, upstream message types do not match downstream handlers, or a TCP pipeline is used with UDP. rs-netty encodes these constraints in builder types:
codec -> inbound* -> business* -> handler -> outbound*
Only methods that are valid in the current state exist. For example, .handler(...) is unavailable before .codec(...); .inbound(...) is unavailable after .handler(...); and the final outbound type must be encodable by the selected codec.
The repository’s trybuild tests cover these failure modes, including:
fail_stream_handler_before_codec.rsfail_stream_outbound_before_handler.rsfail_stream_type_mismatch_inbound_to_handler.rsfail_stream_final_encoder_mismatch.rsfail_udp_with_stream_pipeline.rsfail_tcp_with_datagram_pipeline.rs
Separate TCP And UDP Builders
TCP uses pipeline() and requires codecs to implement Decoder and Encoder<T>. UDP uses datagram_pipeline() and requires codecs to implement DatagramDecoder and DatagramEncoder<T>.
This is a type boundary, not just a naming convention. TcpServer::pipeline accepts only IntoStreamPipeline; UdpServer::pipeline accepts only IntoDatagramPipeline. Mixing TCP and UDP pipeline builders fails at compile time.
Prefer Owned Messages
The public API does not expose Java Netty-style reference-counted ByteBuf. Codecs and handlers use owned types such as String, bytes::Bytes, and user-defined structs. This fits Rust ownership and avoids ref-count lifetime mistakes.
Bounded Queues And Explicit Flush
Channel and DatagramChannel use bounded Tokio mpsc queues internally. outbound_queue_size controls the external command queue size. When the queue is full, write calls wait for capacity instead of growing without bound.
Write and flush are explicit:
writeonly queues or stages data.flushpushes already staged data to the socket.write_and_flushwrites and creates a flush boundary.
This lets throughput-oriented code batch writes while latency-oriented code can flush explicitly.
Zero Unsafe
The crate root uses #![deny(unsafe_code)], and rs-netty-macros does the same. The current library and macro crate do not use unsafe on the main implementation path.