-
Notifications
You must be signed in to change notification settings - Fork 72
Description
Summary: The number of elements read and whether EOF was reached should not be combined into one return value.
I noticed a situation in which I need to know how many elements read-into!
actually read when it reached the end of the stream (that's bug 1) so I started poking around at read
methods, which are often implemented in terms of read-into!
.
Here's the most basic read
method:
define method read (stream :: <stream>, n :: <integer>,
#key on-end-of-stream = unsupplied())
=> (elements)
let elements = make(<vector>, size: n);
if (supplied?(on-end-of-stream))
read-into!(stream, n, elements, on-end-of-stream: on-end-of-stream);
else
read-into!(stream, n, elements);
end;
elements
end method read;
If fewer than n
elements are read, there is no indication that the end of the stream was reached and elements
has elements at the end that should likely be considered invalid. ([later] Maybe <incomplete-read-error>
is signaled indirectly? It's not clear.)
The method on <buffered-stream>
takes the other approach and drops the < n elements on the floor:
define method read
(stream :: <buffered-stream>, n :: <integer>,
#key on-end-of-stream = unsupplied())
=> (elements)
let elements = make(stream-sequence-class(stream), size: n);
let count-or-eof = read-into!(stream, n, elements, on-end-of-stream: on-end-of-stream);
if (count-or-eof == on-end-of-stream)
on-end-of-stream
else
elements
end
end method read;
<sequence-stream>
signals <incomplete-read-error>
:
define method read
(stream :: <sequence-stream>, n :: <integer>,
#key on-end-of-stream = unsupplied())
=> (elements)
ensure-readable(stream);
let seq :: <sequence> = stream-sequence(stream);
let pos :: <integer> = stream.current-position;
let src-n :: <integer> = (stream-limit(stream) | stream.final-position) - pos;
if (n > src-n)
stream.current-position := pos + src-n;
if (unsupplied?(on-end-of-stream))
if (zero?(src-n))
error(make(<end-of-stream-error>, stream: stream))
else
error(make(<incomplete-read-error>,
stream: stream,
count: src-n,
sequence: copy-sequence(seq, start: pos, end: pos + src-n)));
end;
else
on-end-of-stream
end;
else
let elements = copy-sequence(seq, start: pos, end: pos + n);
stream.current-position := pos + n;
elements
end;
end method read;
This still doesn't solve the problem if on-end-of-stream
is supplied; the last "fewer than n" elements are never returned.
In my opinion, use of <incomplete-read-error>
is too complex, in that it expands the I/O API in a non-useful way. Not to mention that in general reading fewer than n bytes is not an error; it's a normal EOF. Instead we should be more similar to C and Go. Specifically, drop the on-end-of-stream
keyword parameter and instead have this:
define generic read (stream, n) => (s :: <sequence>, eof? :: <boolean>);
define generic read-into! (stream, sequence, n, #key start) => (n :: <cardinal>, eof? :: <boolean>);
Typical callers would look like this:
iterate loop (done? = #f)
unless (done?)
let (count, eof?) = read-into!(stream, n, buffer);
do-something(buffer, count);
loop(eof?)
end
end
If we were to depend on <incomplete-read-error>
callers would look something like this:
iterate loop (done? = #f)
unless (done?)
block ()
let count = read-into!(stream, n, buffer)
do-something(buffer, count);
exception (ex :: <incomplete-read-error>)
do-something(buffer, ex.stream-error-count);
done? := #t;
end;
loop(done?)
end
end
But whatever we do, all read
and read-into!
methods should have consistent behavior.