###: Faceted Ports

by Daphne Preston-Kendal, Peter McGoron

Status

For editor's use only. Please do not edit this section.

??? the draft/final/withdrawn status of the SRFI, information on how to subscribe to its mailing list, and important dates in its history. The editor will add this section.

Abstract

An alternative interface for creating custom ports and for accessing information from ports is defined. The interface is more flexible and extensible than the rigid set of port operations defined for custom ports in the R6RS and in its close relative SRFI 181. Some other minor issues with these previous specifications are also corrected. The custom ports defined here are sufficient to implement SRFI 6 / R7RS-Small string ports and SRFI 271 random binary ports.

Issues

  1. (make-overlaid-port) An alternative would be to install an exception handler around the call to proc, raising an exception of its own with a new &port-creation-violation condition type with a field to access the unlocked copy. This might be cleaner even if less flexible.
  2. (port-lock!) [phm]: Should this end with a !?
  3. [phm]: Should there be a peek/lookahead facet? An implementer could hardcode a one-figure lookahead buffer, but this would interact poorly with a feature that one should be able to add onto custom ports, which is the ability to add an arbitrary pushback buffer.
  4. [phm]: Should there be procedures to check if a certain port has certain facets on it?
  5. [phm]: Should facet? be a procedure?
  6. [phm]: Allow char and byte readers? Example: CHICKEN 6.
  7. [phm]: What higher-order procedures, if any, are continuation safe?
  8. [phm]: Should there be more facets for more fine-grained operations? For example, a facet for write-bytevector that in turn depends on write-u8, but can be overridden to be faster.
  9. (make-transcoded-facet) [phm]: Should this be exposed? It would be possible to add this facet to a port without actually transcoding it. Including these procedures would also require supporting the R6RS transcoders, so maybe these be optional for R7RS.
  10. (make-port-facet-type): [phm]: This is somewhat awkward to use. Is there a more succinct way to capture the usual case? One example that comes to mind is to export accessors instead of using an accessor procedure. A version like R7RS's define-record-type could be here, while R6RS systems could subclass a facet object.
  11. (read-bytes-facet, etc.) [phm]: I am not convinced of the argument generators/accumulators. Most programs trust code running in the process, and should not send objects with secrets to untrusted processes anyways. (They can just allocate a bytevector/string and splice-in the read data if they are paranoid.) With regards to string mutation, this could be solved by having the procedures always return either a char or a string. The only unavoidable performance penalty is the rare occurence of get-string-n! (in R6RS). Otherwise the procedures will allocate strings anyways. Performance penalties can also be obviated by allowing for facets that implement certain procedures like get-line themselves.
  12. (make-close-input-facet) Maybe disallow bidirectional ports.
  13. (make-line-buffered-input-port) [phm]: Should this procedure have a configurable EOL?
  14. (make-line-buffered-output-port, make-block-buffered-output-port) Is automatically flushing the Right Thing? What should happen if the port position is set and the buffer is not empty? Automatically flush, or signal an error?
  15. (make-size-limited-input-port, make-size-limited-output-port): Should this signal an error of some more specific type?

Rationale

The custom ports of the R6RS and SRFI 181 provide useful functionality, but in a fixed and inflexible way. They support only a fixed set of port operations. The faceted ports proposal provides a flexible and extensible algebra for creating efficient I/O interfaces.

The relation between ports and facets in this SRFI can be thought of as similar to the relation between compound and simple conditions in R6RS, or compounds and components in SRFI 222. However, unlike in condition objects, a port is not a facet, and a facet is not a port: the two form distinct layers of the interface. It is not possible to compound two ports together into a single port – you cannot compound an input port and an output port into a single bidirectional port – for reasons including that the port position state of the two underlying ports would be out of synch with one another once any reading or writing had taken place. Therefore it is also not possible to extract a list of the facets from a port after creating it: you must only interact with the port using the public API. To ensure this, it is also not possible to extract the field values of a facet from the facet itself: it must be compounded into a port first to provide a public API.

In a sense, one can think of facets as an arbitrarily-extensible set of keyword arguments to the make-port operation: the behaviour of them and restrictions on them are compatible with this interpretation.

Compared to R6RS and SRFI 181, this interface allows ports to be extended with arbitrary properties and operations.

Specification

Note: As well as providing custom ports, the R6RS also completely redesigned the other I/O procedures of Scheme. Notable about the R6RS system is:

Any procedure from R6RS, R7RS-Small, or any SRFI which takes a port as an argument can be used with the ports created by the procedures of this SRFI. The specification here uses the R7RS-Small names of procedures as examples but otherwise takes no position on which system is preferable.

This proposal does not directly deal with the creation of ports from operating system resources. Preliminarily, it is suggested that the following standard procedures have the properties in this table. More efficient userspace-level buffering should then be provided by using the buffering-related port creation helper procedures. All of these procedures should also make appropriate informative facets available, if possible.

Procedure Description
open-binary-input-file, open-binary-output-file (R7RS-Small) Open a raw binary port without buffering beyond what the operating system provides. In POSIX terms, where read-bytes-facet/write-bytes-facet directly calls read/write, the close system call directly in their close-input-facet or close-output-facet, and the fsync system call directly in their flush-output-facet.
open-input-file equivalent to (make-transcoded-port (open-input-file STRING) (native-transcoder))
open-output-file equivalent to (make-transcoded-port (open-input-file STRING) (native-transcoder))
open-file-input-port, open-file-output-port, open-file-input/output-port (R6RS) equivalent to setting up such a raw port and wrapping it in corresponding buffering and transcoded ports, although their full functionality cannot be provided in terms of the R7RS-Small procedures with only the wrapping functionality of this SRFI (in particular, input/output ports and the file-options arguments cannot be implemented with R7RS-Small)
standard-input-port, standard-output-port, standard-error-port (R6RS) should return raw binary ports with only operating system buffering, like the suggestion for the open-binary-output-file procedures.

Conventions

In procedure signature, square brackets [] denote a group of optional arguments that must either be all present or all absent. Nested square brackets denote parts of that group that are optional even when other arguments in the group are present. For example, (make-port-position-facet [getter [setter]]) is a procedure signature for make-port-position-facet that can be called with zero arguments, one argument getter, or two arguments getter and setter.

This SRFI uses the term ‘assertion violation’ for certain erroneous uses. An R7RS implementation should raise an error in those cases.

The procedures from this SRFI are exported from (SRFI :### faceted-ports ⟨sublibrary⟩) (SRFI 97 convention) or (SRFI ### ⟨sublibrary⟩) (R7RS convention). By default, the procedures are exported from the main library.

Library summary

(srfi ###)

make-port, make-overlaid-port, port-lock! make-read-bytes-facet, read-bytes-facet?, make-write-bytes-facet, write-bytes-facet?, make-read-bytes-facet, read-bytes-facet?, make-write-bytes-facet, write-bytes-facet?, make-close-input-facet, close-input-facet?, make-close-output-facet, close-output-facet?, port-position-facet, port-position-facet?, make-u8-ready?-facet, u8-ready?-facet?, make-char-ready?-facet, char-ready?-facet?, make-flush-output-facet, flush-output-facet?, file-name-facet, file-name-facet?, port-file-name, make-file-descriptor-facet, file-descriptor-facet?, port-file-descriptor, make-port-facet-type

(srfi ### buffered)

make-line-buffered-input-port, make-line-buffered-output-port, make-block-buffered-input-port, make-block-buffered-output-port

(srfi ### size-limited)

make-size-limited-input-port, make-size-limited-output-port

(srfi ### transcoding)

make-transcoded-port, make-transcoder-facet, make-transcoder-facet?, port-transcoder

(srfi ### compound)

make-input-output-port, make-broadcast-port, make-concatenated-port

(srfi ### r7rs)

TODO

(srfi :### r6rs io)

TODO

(srfi :### r6rs simple)

TODO

Glossary of concepts

Port directionality

Ports can be input ports (supporting procedures such as write-bytevector or write-string), output ports (supporting procedures such as read-bytevector or read-string), or bidirectional ports (supporting both kinds of procedures).

Port figure type

Ports can have either of two figure types: binary or textual.

The word ‘figure’ is used here in an application of its sense as a synonym for terms such as ‘character’, (alphabet) ‘symbol’, ‘sign’, ‘glyph’, etc. That is, ‘figure’ is to be understood as referring either an exact integer object in the range of 8-bit unsigned bytes, or for a character object, depending on which is appropriate for a particular port. This term was chosen because, as of the writing of this specification, it is not used in any other context in computing in a sense which would be likely to cause confusion with some other concept related to processing textual or binary data, nor is it used within Scheme to refer to any other existing type.

Each port has only one figure type. A bidirectional port must either be binary in both directions or textual in both directions.

Port locking

Port locking is a concept introduced by the R6RS, which this SRFI generalizes and given a useful name. Port locking is used to control access to a port which is now ‘wrapped’ by another port: anything which holds a copy of the original port can no longer use it for further reading and writing; the process which wraps the new port receives a new, unlocked copy which it exposes access to through some new and presumably more featureful port interface. There is no general way to convert a locked port back into an unlocked port, although specific facet types could hypothetically offer this.

In R6RS, port locking is used in one way only, by the transcoded-port procedure which wraps a binary port and turns it into a textual port:

As a side effect, however, transcoded-port closes binary-port in a special way that allows the new textual port to continue to use the byte source or sink represented by binary-port, even though binary-port itself is closed and cannot be used by the input and output operations described in this chapter. — R6RS Standard Libraries, pg. 34

This ‘special way’ of closing a port is what is referred to here as port locking.

Facets

Facets add functionality to ports. A port is simply a collection of facets. Each facet belongs to a specific facet type: new facet types can be defined, and each port can only have one facet of each type. Facet types are divided into several kinds: there are a small number of ‘essential facets’, of which every port must have exactly one or two, which provide basic reading and/or writing. ‘Utility facets’ provide additional functionality, such as closing ports, getting and setting port position, and flushing output. ‘Informational facets’ return the value of properties associated with each port. There are some restrictions on how essential facets can be combined. It is not possible to define new types of essential facets.

Underlying and overlaying ports

Underlying and overlaying of ports is a convenience concept built on the concept of port locking, and generalize the notion of transcoded ports in the R6RS. A new port can be overlaid on an existing port, which is locked. The existing port is called the underlying port. An overlaid port provides a higher-level view on the underlying port, or additional functionality such as buffering. Unlike in the R6RS, a binary port can be overlaid on a binary port, a textual port can be overlaid on a textual port, and a binary port can even be overlaid on a textual port.

When one port is overlaid on another, the informative and utility facets of the underlying port continue to be available on the overlaying port.

Generators and accumulators

The generators and accumulators of SRFI 158 are used to access the buffers for reading and writing data instead of bytevectors and strings (and/or vectors of characters) as in the R6RS and SRFI 181.

For ports of either figure type, this brings a security advantage: a port’s internal reader or writer operation can cheaply be restricted from looking outside of its designated area in a (re-used) buffer and possibly seeing secret strings, leftover from some previous operation in the buffer, which it should not have access to.

For textual ports, this avoids using deprecated string mutation operations for input ports, and allows implementations to use a flexible and efficient representation of buffered characters, opaque to the port implementation. In particular, the vector of characters approach suggested by SRFI 181 wastes at least 4 bytes of storage per character on 64-bit machines (increasing to 7 bytes per character for ASCII text), and unnecessarily taxes the garbage collector with scanning a vector which never contains collectable objects. A u32vector would alleviate these problems, but generators and accumulators allow implementation to use UTF-8 internally and don’t require constant use of the char->integer and integer->char procedures.

However, the use of generators and accumulators means that ports must read and write to and from ports in order. Unlike in the R6RS and SRFI 181, random or reversed reading and writing is not possible. Given that the R6RS does not make any guarantees that would make out-of-order reading and writing from buffers useful anyway, this is not felt to be a great loss.

Creating ports

(make-port facet …) procedure

Creates a new port which has the given facets. The new port does not overlay any port. The facets must all have distinct facet types. If any of the facet objects has previously been used to create another port, it is an assertion violation.

(make-overlaid-port underlying-port proc) procedure

Create a new port overlaying the given underlying-port, which must be an unlocked port. proc is called with an unlocked copy of underlying-port as its argument, and should return a list of facets which the newly-created port will have. The list must not contain multiple facets of the same facet type. Before make-overlaid-port returns but after it has called proc, the underlying-port is locked.

Rationale: If constructing any of the facets raises an exception, underlying-port should not be left in a locked state where there is no corresponding unlocked port on which I/O operations can be resumed.

(port-lock! port) procedure

Locks the given port and returns an unlocked copy of the port. The port must not be locked already.

Locking a port prevents any operation which uses an essential or utility facet from being performed on that port. The unlocked copy must be used for all such future operations.

This is a low-level operation: the automatic locking of underlying ports provided by make-overlaid-port should be used in the majority of situations. However, there are circumstances where a port must be created which has one or more ports conceptually underlying it, in a way which the formal mechanism of underlying ports provided by make-overlaid-port cannot support. (See the section ‘Port creation helper procedures’ for numerous examples of such ports.)

Essential facets

Every port must contain at least one of these facets. Furthermore, a read-bytes-facet can be compounded together with a write-bytes-facet or vice versa; and a read-chars-facet can be compounded together with a write-chars-facet or vice versa; but no other combination of these essential facets is allowable. An assertion violation should be signalled by make-port if none of these facets are present, or if a combination of facets is used which violates this restriction.

(make-read-bytes-facet read-proc) procedure
(read-bytes-facet? obj) procedure

The argument read-proc is a procedure of two arguments, count and accumulator.

When procedures such as read-bytevector are called on a port which has a read-bytes-facet, the read-proc of the facet will be called with a value of count representing the number of bytes the implementation wants to read from the port. The read-proc should call the accumulator a maximum of count times, each time with a single byte which is read from the port. If the port reaches the EOF state before count bytes have been fed into the accumulator, the accumulator should be called once with the EOF object and then never again subsequently.

It is an assertion violation if the read-proc calls the accumulator fewer than count times when the final call was not with the EOF object as its argument. It is also an assertion violation if the read-proc calls the accumulator more than count times.

This facet is used by the R7RS procedures read-u8, read-bytevector, read-bytevector!, and the R6RS procedures get-u8, get-bytevector-n, get-bytevector-n!, get-bytevector-some, and get-bytevector-all.

(make-write-bytes-facet write-proc) procedure
(write-bytes-facet? obj) procedure

write-proc is a procedure of two arguments, count and generator:

When procedures such as write-bytevector are called on a port which has a write-bytes-facet, the write-proc is called with a value of count representing the number of bytes the implementation wants to spit into the port. The write-proc should call the generator a maximum of count times; each call will return a new byte to be written to the port. If an EOF state is to be sent to the port, the generator will return the EOF object; all subsequent calls to the generator will then also return the EOF object.

It is an assertion violation if the write-proc calls the generator fewer than count times when the final call did not return the EOF object. It is also an assertion violation if the write-proc calls the generator more than count times.

(make-write-chars-facet write-proc) procedure
(write-chars-facet? obj) procedure

write-proc is a procedure of two arguments, count and generator:

When procedures such as write-bytevector are called on a port which has a write-bytes-facet, the write-proc is called with a value of count representing the number of characters the implementation wants to spit into the port. The write-proc should call the generator a maximum of count times; each call will return a new character to be written to the port. If an EOF state is to be sent to the port, the generator will return the EOF object; all subsequent calls to the generator will then also return the EOF object.

It is an assertion violation if the write-proc calls the generator fewer than count times when the final call did not return the EOF object. It is also an assertion violation if the write-proc calls the generator more than count times.

Utility facets

Utility facets add additional I/O-related functionality to a port beyond basic reading and writing.

(make-close-input-facet close-proc) procedure
(close-input-facet? obj) procedure

The close-proc argument is a procedure of no arguments. When close-port or close-input-port is called on a port with a close-input-facet, the close-proc is called to close the port.

Note: This is incompatible with R6RS and SRFI 181, where bidirectional ports are closed in both directions at once. However, it may be advantageous in some cases to close them at different times, in particular with sockets on POSIX, which are bidirectional file descriptors where (close-input-port sock) should call shutdown(sock_fd, SHUT_RD), (close-output-port sock) should call shutdown(sock_fd, SHUT_WR), and (close-port sock) should call shutdown(sock_fd, SHUT_RDWR).

(make-close-output-facet close-proc) procedure
(close-output-facet? obj) procedure

The close-proc argument is a procedure of no arguments. When close-port or close-output-port is called on a port with a close-output-facet, the close-proc is called to close the port.

Note: See close-input-facet.

(port-position-facet [getter [setter]]) procedure
(port-position-facet? obj) procedure

The getter argument is a procedure of no arguments; setter is a procedure of one argument, an exact nonnegative integer. When port-position is called on a port that has a port-position-facet, it returns the value of calling the getter procedure; when set-port-position! is called on a port that has a port-position-facet, it calls setter with the new position that should be set.

If no setter is provided, the port will support getting the port position but not setting it. The port-has-set-port-position!? procedure from R6RS/SRFI 192 will return #f on the port. If neither getter nor setter is provided, the port will not support getting nor setting the port position, and the port-has-port-position? and port-has-set-port-position!? procedures will both return #f as if no port-position-facet were present: this is useful when creating a port which overlays another port when the additional abstraction added by the overlaying port causes port positioning to no longer be meaningful.

(make-u8-ready?-facet ready?-proc) procedure
(u8-ready?-facet? obj) procedure

The ready?-proc argument is a procedure of no arguments. When u8-ready? is called on a port that has a u8-ready?-facet, it returns the value of calling the ready-proc.

Note: This is not compatible with R6RS and SRFI 181, which simply do not provide a way to check u8-ready? on a custom port. (Indeed, R6RS does not provide u8-ready? nor char-ready? at all.) A portable implementation of this SRFI will have to re-export its own version of u8-ready?.

(make-char-ready?-facet ready?-proc) procedure
(char-ready?-facet? obj) procedure

The ready?-proc argument is a procedure of no arguments. When char-ready? is called on a port that has a char-ready?-facet, it returns the value of calling the ready-proc.

Note: See u8-ready-facet.

(make-flush-output-facet flush-proc) procedure
(flush-output-facet? obj) procedure

The flush-proc argument is a procedure of no arguments. When flush-output-port is called on a port that has a flush-output-facet, it calls the flush-proc. If flush-output-port is called on a port which does not have a flush-output-facet, it is a no-op.

Note: R6RS does not provide a way for custom ports to provide a flush-output-port operation, but SRFI 181 does.

Informative facets

(make-file-name-facet file-name) procedure
(file-name-facet? obj) procedure
(port-file-name port) procedure

This facet allows the name of the file to be retrieved from a port object which is reading from and/or writing to that file. file-name must be a string. The port-file-name procedure, called on a port which has a file-name-facet facet, returns this string.

Note: This corresponds to the id argument to the procedures to make custom ports in R6RS and SRFI 181. In implementations built on top of R6RS, the id will be set to the empty string if there is no file-name-facet. In implementations built on top of SRFI 181, the id will be set to #f in this case. (R6RS requires that the id be a string; SRFI 181 allows it to be any arbitrary object. Neither provides a means of getting the file name back out of a port in any case, so the trick suggested in the Implementation section will be needed to implement the port-file-name procedure.)

(make-file-descriptor-facet fd) procedure
(file-descriptor-facet? obj) procedure
(port-file-descriptor port) procedure

On operating systems where open files, pipes, sockets, etc. are represented by integer file descriptors, this facet allows the number of the file descriptor to be retrieved from the port. The fd argument must be an exact nonnegative integer. The port-file-descriptor procedure, called on a port which has a file-descriptor-facet, returns this integer.

Note: An important rule for programs in high-level languages such as Scheme is ‘don’t mess with file descriptors you aren’t given’. If you are writing a program according to a certain protocol (e.g. UCSPI) which you know gives you certain ports at startup, you can use those; you have been given them, and you can convert them to ports with e.g. fd->port from SRFI 170. But once a port has been opened, a Scheme implementation will feel that it has full control over buffering output to it, getting and setting the port position, etc., and will not want or expect anything else to go messing with the file descriptor underlying it. There may be a limited set of operations which it would make sense to do through an FFI on such a file descriptor (e.g. fstat(2), isatty(3)), but in general a file descriptor, once wrapped in a Scheme port object, should only be accessed through that port, to prevent undefined behaviour.

(make-transcoder-facet transcoder) (SRFI ### transcoding) procedure
(transcoder-facet? obj) (SRFI ### transcoding) procedure
(port-transcoder port) (SRFI ### transcoding) procedure

On transcoded ports, this facet allows the transcoder to be retrieved from the port. The transcoder argument must be a transcoder. The port-transcoder procedure, called on a port which has a transcoder-facet, returns this transcoder.

Creating new facet types

(define-port-facet-typefacet name⟩
  (⟨constructor name⟩ ⟨field name1⟩ …)
  ⟨predicate name⟩
  ⟨accessor name⟩)syntax

Syntax: Each of ⟨facet name⟩, ⟨constructor name⟩, ⟨predicate name⟩, ⟨accessor name⟩ and all the ⟨field name⟩s are identifiers.

Semantics: Defines ⟨facet name⟩ as a new type of port facet, distinct upon each evaluation of a define-port-facet-type form from all existing port facet types. The facet has as many fields as there are ⟨field name⟩s. The ⟨facet name⟩ may be bound as a syntax keyword or to some other unspecified type of runtime value.

The ⟨constructor name⟩ is bound to a constructor procedure of as many arguments as there are ⟨field name⟩s. When called, this procedure returns a newly-allocated port facet of the created type whose fields are set to the respective values of the arguments.

The ⟨predicate name⟩ is bound to a predicate procedure of one argument. It returns #t if the argument is a port facet of the newly-created type, or #f if the argument is any other object.

The ⟨accessor name⟩ is bound to an accessor procedure of two mandatory arguments, port and facet-proc, and one optional argument, no-facet-proc. When called on a given port which has a facet of the newly-created type, the accessor procedure tail-calls the procedure facet-proc with the arguments that were given when the constructor procedure was called. When called on a port which does not have a facet of the created type, the accessor procedure tail-calls the procedure no-facet-proc with no arguments; if the no-facet-proc argument was not given, it signals an assertion violation.

Port creation helper procedures

These procedures provide R6RS-compatible means of controlling I/O buffering, equivalents of some of Common Lisp’s built-in stream types in Scheme, and some other useful utilities.

Buffering

(make-line-buffered-input-port underlying-port) (SRFI ### buffered) procedure

Creates a new input port, overlaying the input port underlying-port, which reads at least a whole ‘line’ full of figures from the underlying port into an internal buffer in the store in one go. The returned input port will feed its consumer data from this buffer before going to the underlying-port to obtain more data.

A ‘line’ is a sequence of figures of a size determined by the figures themselves. A line ends when a particular figure is seen. The internal buffer may extend beyond the current line.

If the underyling-port is a binary port, the bytes #x0, #xA, and #xD, and the sequence #xD #xA, are considered line terminators. If it is a textual port, the characters #\newline, #\return, and the sequence "\r\n" are considered line terminators.

Note to implementers: The returned port should wrap port position setting on the underlying-port so that the input buffer is cleared before the position is really set.

If underlying-port is bidirectional, the returned port will be bidirectional and will simply pass through information from the output side without buffering.

(make-line-buffered-output-port underlying-port) (SRFI ### buffered) procedure

Creates a new output port, overlaying the output port underlying-port, which stores a whole ‘line’ full of the figures which are written to it to a buffer in the store, before sending the whole line to the underlying port in one go. The flush-output-port procedure is automatically called on the underlying port after each line is written to it. Additionally, the flush-output-port procedure called on such a port can be used to send written figures to the underlying port and empty the buffer before a whole line has been written to the returned port.

Lines are defined as for make-line-buffered-input-port.

If underlying-port is bidirectional, the returned port will be bidirectional and will simply pass through information to the input side without buffering.

(make-block-buffered-input-port underlying-port [buffer-size]) (SRFI ### buffered) procedure

Creates a new input port, overlaying the input port underlying-port, which reads up to buffer-size figures from the underlying port into an internal buffer in the store in one go. The returned input port will feed its consumer data from this buffer before going to the underlying-port to obtain more data.

Note to implementers: The returned port should wrap port position setting on the underlying-port so that the input buffer is cleared before the position is really set.

If buffer-size is not given, an unspecified default size is used. For textual ports, the number of characters stored in the buffer may be smaller than the buffer-size.

If underlying-port is bidirectional, the returned port will be bidirectional and will simply pass through information from the output side without buffering.

(make-block-buffered-output-port underlying-port [buffer-size]) (SRFI ### buffered) procedure

Creates a new output port, overlaying the output port underlying-port, which stores up to buffer-size figures (a block of figures) written to it to a buffer in the store, before sending all of those figures to the underlying port in one go when that many figures have been written. The flush-output-port procedure is automatically called on the underlying port after each block is written to it. Additionally, the flush-output-port procedure called on such a port can be used to send written figures to the underlying port and empty the buffer before a whole block has been written to the returned port.

If buffer-size is not given, an unspecified default size is used. For textual ports, the number of characters stored in the buffer may be smaller than the buffer-size.

If underlying-port is bidirectional, the returned port will be bidirectional and will simply pass through information to the input side without buffering.

Size-limited ports

(make-size-limited-input-port underlying-port limit) (SRFI ### size-limited) procedure

The limit argument must be an exact nonnegative integer.

Creates a new input port overlaying the underlying-port. After limit figures have been read from the port, further attempts to read from the port will signal an assertion violation.

This procedure provides a generalized solution to the security problems of reading limitless amounts of data from untrusted sources, and is intended to succeed ad hoc solutions such as the json-number-of-character-limit parameter from SRFI 180.

(make-size-limited-output-port underlying-port limit) (SRFI ### size-limited) procedure

The limit argument must be an exact nonnegative integer.

Creates a new output port overlaying the underlying-port. After limit figures have been written to the port, further attempts to write to the port will signal an assertion violation.

Transcoded ports

(make-transcoded-port underlying-port transcoder) (SRFI ### transcoding) procedure

The underlying-port procedure must be a binary port.

Returns a new textual port which overlays the binary port underlying-port. Input to and/or output from the underlying-port will be transcoded using the given transcoder.

Compound ports

(make-input/output-port input-port output-port) (SRFI ### compound) procedure

The input-port and output-port must have the same figure type.

The input-port and output-port will be locked by this operation, but the returned port does not formally overlay either the input-port nor the output-port, so any other facets of those ports will not be visible on the returned port.

(make-broadcast-port output-port …) (SRFI ### compound) procedure

All of the output-ports must have the same figure type.

Returns a new output port which writes anything written to it to all of the given output-ports in sequence.

All of the output-ports will be locked by this operation, but the returned port does not formally overlay any of the output-ports, so any other facets of those ports will not be visible on the returned port.

(make-concatenated-port input-port …) (SRFI ### compound) procedure

All of the input-ports must have the same figure type.

Returns a new input port which reads from each of the given input-ports in sequence.

All of the input-ports will be locked by this operation, but the returned port does not formally overlay any of the input-ports, so any other facets of those ports will not be visible on the returned port.

R7RS-Small compatability

For implementations that support the R7RS, the library (SRFI ### r7rs) exports all procedures defined in the R7RS § 6.13, except that they work with faceted ports. Implementations are encouraged to make their default port objects faceted ports, where the identifiers exported from this library are the same as the ones exported from the standard libraries.

R6RS compatability

For implementations that support the R6RS, the libraries (SRFI :### faceted-ports r6rs port) and (SRFI :### faceted-ports r6rs simple) export all identifiers defined in the R6RS Standard Libraries, Chapter 8, except that they work with faceted ports. Implementations are encouraged to make their default port objects faceted ports, where the identifiers exported from this library are the same as the ones exported from the standard libraries.

Implementation

Except for the minor problems noted in some entries, it is possible to implement this proposal on top of R6RS or SRFI 181. Informative facets which are not part of the specification of custom ports in R6RS/SRFI 181 can be implemented by storing them in a hash table (weak-keyed, ideally, so that ports are still collectable).

An implementation for CHICKEN 6 is available in the SRFI repository. Note that this is a re-implementation of Scheme ports and hence does not interoperate with the standard Scheme port procedures.

Acknowledgements

Dimitris Vyzovitis’s design for sources and sinks in Gerbil was an inspiration for this proposal. The design of I/O in Python was another source of inspiration. Jani Juhani Sinervo’s port properties proposal suggested the inclusion of the informative facets.

Daphne Preston-Kendal wrote the whole SRFI. Peter McGoron wrote a CHICKEN implementation and edited the text of the SRFI into HTML.

© 2026 Daphne Preston-Kendal, Peter McGoron.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Editor: Arthur A. Gleckler