I'm too dumb for Zig's new IO interface
You might have heard that Zig 0.15 introduces a new IO interface, with the focus for this release being the new std.Io.Reader and std.Io.Writer types. The old "interfaces" had problems. Like this performance issue that I opened. And it relied on a mix of types, which always confused me, and a lot of anytype - which is generally great, but a poor foundation to build an interface on.
I've been slowly upgrading my libraries, and I ran into changes to the tls.Client client used by my smtp library. For the life of me, I just don't understand how it works.
Zig has never been known for its documentation, but if we look at the documentation for tls.Client.init , we'll find:
pub fn init ( input : * std . Io . Reader , output : * std . Io . Writer , options : Options ) InitError ! Client Initiates a TLS handshake and establishes a TLSv1 . 2 or TLSv1 . 3 session .
So it takes one of these new Readers and a new Writer, along with some options (sneak peak, which aren't all optional). It doesn't look like you can just give it a net.Stream , but net.Stream does expose a reader() and writer() method, so that's probably a good place to start:
const stream = try std . net . tcpConnectToHost ( allocator , "www.openmymind.net" , 443 ) ; defer stream . close ( ) ; var writer = stream . writer ( & . { } ) ; var reader = stream . reader ( & . { } ) ; var tls_client = try std . crypto . tls . Client . init ( reader . interface ( ) , & writer . interface , . { } , ) ;
Note that stream.writer() returns a Stream.Writer and stream.reader() returns a Stream.Reader - those aren't the types our tls.Client expects. To convert the Stream.Reader to an *std.Io.Reader , we need to call its interface() method. To get a *std.io.Writer from an Stream.Writer , we need the address of its &interface field. This doesn't seem particularly consistent. Don't forget that the writer and reader need a stable address. Because I'm trying to get the simplest example working, this isn't an issue - everything will live on the stack of main . In a real word example, I think it means that I'll always have to wrap the tls.Client into my own heap-allocated type; giving the writer and reader have a cozy stable home.
Speaking of allocations, you might have noticed that stream.writer and stream.reader take a parameter. It's the buffer they should use. Buffering is a first class citizen of the new Io interface - who needs composition? The documentation does tell me these need to be at least std.crypto.tls.max_ciphertext_record_len large, so we need to fix things a bit:
var write_buf : [ std . crypto . tls . max_ciphertext_record_len ] u8 = undefined ; var writer = stream . writer ( & write_buf ) ; var read_buf : [ std . crypto . tls . max_ciphertext_record_len ] u8 = undefined ; var reader = stream . reader ( & read_buf ) ;
... continue reading