silk.io.writer_native.
Native Writer providers for standard output and standard error, and a lock for whole messages
on standard error.
When to use#
Provide StdoutWriter or StderrWriter for Writer in a native program. Code that writes
uses silk.io.writer, so a test can provide a writer to memory instead.
Use StderrWriter.print for a debug message on standard error. Use StderrWriter.lock when
several writes must reach standard error as one message, with no output of other threads
between them.
Details#
StdoutWriter and StderrWriter have no buffer. Each write writes all bytes to the
descriptor, and flush does nothing. The process owns the descriptors, and the providers do not
close them.
Writer.writeVectored sends several slices in one writev system call, up to eight slices for
each call.
WASI commands have one thread. Their standard error lock does not need host synchronization.
Standard error has one lock for the whole process: the lock of the C library's stderr stream.
Each StderrWriter write takes it for the duration of that write. StderrWriter.lock keeps
it until the LockedStderr drops, and collects the writes in a small buffer that the caller
lends. The lock is recursive: the thread that holds it can take it again, for example to report
a failure in the middle of a message. Standard output has no lock. A program that writes to it
from several threads controls the order itself.
Errors#
WriterError if a write fails. Part of the bytes can be written already.
Gotchas#
The providers exist on WASI, Darwin on aarch64, and Linux with GNU libc on x86_64 or aarch64.
The lock orders only writes that take it. A direct write to descriptor 2, such as one from
silk.platform.native.descriptor, can come between the parts of a locked message.
Examples#
Write to standard output#
import silk.core.effect { Effect }
import silk.io.writer_native { StdoutWriter }
import silk.io.writer { Writer, WriterError }
pub effect fn main() -> i32
! WriterError {
let mut out = StdoutWriter.make()
run Writer.writeString("Silk\n") |> Effect.provideMut<Writer>(&mut out)
return 0
}Print a debug message#
import silk.io.writer_native { StderrWriter }
pub fn main() -> i32 {
let values = .{ name: "cache", hits: 3 }
StderrWriter.print("{name}: {hits} hits\n", &values)
return 0
}Import as StdoutWriter with import silk.io.writer_native { StdoutWriter }.
Public declarations: 3.
StdoutWriter#
pub struct StdoutWriterA Writer for standard output.
Details#
It has no buffer. Each write writes all bytes to the descriptor, and flush does nothing. The
process owns the descriptor, and the writer does not close it. Writer.writeVectored sends up
to eight slices in one writev system call.
Gotchas#
Standard output has no lock. A program that writes to it from several threads controls the order itself.
StderrWriter#
pub struct StderrWriterA Writer for standard error.
Details#
WASI commands have one thread, so their standard error lock requires no host synchronization.
Each write takes the standard error lock for its duration. Thus one write never comes in the
middle of a message that another thread holds with StderrWriter.lock. It has no buffer, and
flush does nothing. The process owns the descriptor, and the writer does not close it.
Gotchas#
The lock orders only writes that take it. A direct write to descriptor 2, such as one from
silk.platform.native.descriptor, can come between the parts of a locked message.
Implementation StdoutWriter#
impl StdoutWriterAssociated function make#
pub fn make() -> StdoutWriterReturns a writer. It does not write.
Implementation StderrWriter#
impl StderrWriterAssociated function make#
pub fn make() -> StderrWriterReturns a writer. It does not write.
Associated function lock#
pub fn lock<'a>(buffer: &'a mut [u8]) -> LockedStderr<'a>Takes the standard error lock and returns a Writer that collects writes in buffer.
When to use#
Use lock when several writes must reach standard error together, such as a formatted line.
For one formatted message, StderrWriter.print is shorter.
Details#
The call waits until no other thread holds the lock. The LockedStderr keeps the lock until
it drops, and then writes the bytes that are left in buffer. A write that does not fit in
the free part of buffer goes out at once, together with the buffered bytes in one system
call. Thus a buffer of 64 bytes gives one system call for most short lines. An empty buffer
gives one system call for each write.
WASI commands have one thread, so the lock requires no host synchronization.
The lock is recursive. A second lock on the same thread returns at once, and its bytes go
out before the bytes that the first LockedStderr still holds.
Gotchas#
Keep the lock for a short time. Each other thread that writes to standard error waits.
The bytes in
bufferare lost if the program traps before the drop.
Examples#
Write one line from several parts#
import silk.core.effect { Effect }
import silk.text.format { Format }
import silk.io.writer_native { StderrWriter }
import silk.io.writer { Writer, WriterError }
effect fn report(count: i32) -> ()
! WriterError
? &mut Writer {
run Writer.writeString("worker: ")
run Format.format("{} jobs left", &(count,))
return run Writer.writeString("\n")
}
pub effect fn main() -> i32
! WriterError {
let mut buffer: [u8; 64] = [0; 64]
let mut stderr = StderrWriter.lock(&mut buffer)
// No other thread can write to standard error between the three parts.
run report(3) |> Effect.provideMut<Writer>(&mut stderr)
run Writer.flush() |> Effect.provideMut<Writer>(&mut stderr)
return 0
}Associated function print#
pub fn print<Args>(static template: string<'static>, args: &Args) -> ()Formats template with args and writes it to standard error as one message. It ignores
failures.
When to use#
Use print for a debug message, such as a value during a search for a bug. For program
output, provide a Writer. For log events, use silk.io.logger.
Details#
The template rules are those of silk.text.format.Format.format, and the compiler checks them.
The call holds the standard error lock while it formats and writes, so messages of different
threads do not mix. It formats into a buffer of 64 bytes on the stack. Thus a short message is
one system call, and the call does not allocate. It adds no newline.
Gotchas#
A failed write is not reported. Part of the message can be written already.
Examples#
Print a value with its name#
import silk.io.writer_native { StderrWriter }
pub fn main() -> i32 {
let width = 80
StderrWriter.print("width = {}\n", &(width,))
return 0
}LockedStderr#
pub struct LockedStderr<'a>A Writer for standard error that holds the standard error lock until it drops.
Details#
Make one with StderrWriter.lock. It collects writes in the buffer that the caller lent, and
writes them when the buffer is full, on Writer.flush, and when it drops. A drop ignores a
failure. Flush first to see it.
Implementation LockedStderr<'a>#
impl<'a> LockedStderr<'a>Implementation Writer for LockedStderr<'a>#
impl<'a> Writer for LockedStderr<'a>Operation writeAll#
effect fn writeAll(self: &mut Self, val: &[u8]) -> () ! WriterError ? &mut WriterOperation flush#
effect fn flush(self: &mut Self) -> () ! WriterError ? &mut WriterOperation writeVectored#
effect fn writeVectored(self: &mut Self, vals: &[&[u8]]) -> () ! WriterError ? &mut WriterImplementation Drop for LockedStderr<'a>#
impl<'a> Drop for LockedStderr<'a>Operation drop#
fn drop(self: &mut LockedStderr<'a>) -> ()Implementation Writer for StdoutWriter#
impl Writer for StdoutWriterOperation writeAll#
effect fn writeAll(self: &mut Self, val: &[u8]) -> () ! WriterError ? &mut WriterOperation flush#
effect fn flush(self: &mut Self) -> () ! WriterError ? &mut WriterOperation writeVectored#
effect fn writeVectored(self: &mut Self, vals: &[&[u8]]) -> () ! WriterError ? &mut WriterImplementation Writer for StderrWriter#
impl Writer for StderrWriterOperation writeAll#
effect fn writeAll(self: &mut Self, val: &[u8]) -> () ! WriterError ? &mut WriterOperation flush#
effect fn flush(self: &mut Self) -> () ! WriterError ? &mut WriterOperation writeVectored#
effect fn writeVectored(self: &mut Self, vals: &[&[u8]]) -> () ! WriterError ? &mut WriterCategory: Input and output.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu, wasm32-wasip1, wasm32-wasip2.