std.csv.Parser
class pub Parser[I: mut + Read]
A type for parsing a CSV stream.
A Parser
can parse any type that implements std.io.Read
, and offers two
ways of parsing the data:
- A low-level API optimized for low memory usage and control over the parsing process
- A high-level iterator API that prioritises ease of use of control and memory usage
The Parser
type is capable of handling both \r\n
and \n
as the line
endings. Lines that end with just \r
are considered invalid and result in
the parser producing an Error
.
The default separator used is a comma, this can be changed using the method
Parser.separator=
.
Low-level parsing
The low-level API gives full control over the parsing process and makes it possible to parse large CSV streams while keeping memory usage as low as possible.
Parsing using the low-level API is done using the following methods:
Parser.column?
to check if there's data to parse as a columnParser.column
to parse column data as aString
Parser.next_line
to advance the stream to the next line
A typical parsing loop (ignoring proper error handling for the sake of clarity) using this API looks as follows:
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz\r\n1,2,3'))
let rows = []
loop {
let row = []
# Parse each column, until we reach the end of the line.
while parser.column?.get { row.push(parser.column.get) }
# This ensures empty rows don't result in an empty `[]`, which isn't useful.
if row.size > 0 { rows.push(row) }
# Advance to the next line, and break the loop if there's no more data to
# parse.
if parser.next_line.get.false? { break }
}
If the number of columns and their positions is known, you can also write the following:
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz\r\n1,2,3'))
let rows = []
loop {
# This ensures that if the row is entirely empty we just skip it, instead
# of producing an error.
if parser.column?.get {
rows.push([parser.column.get, parser.column.get, parser.column.get])
}
if parser.next_line.get.false? { break }
}
You can also skip columns and entire lines using Parser.skip_column
and
Parser.skip_line
respectively, and skip multiple columns in a single call
using Parser.skip_columns
.
High-level parsing
For cases where the CSV data is small, or you just want to parse something
quickly and don't care about the amount of memory used, the low-level API can
be a bit verbose. To make dealing with such cases easier, the Parser
type
implements std.iter.Iter
. For example, to iterate over the results of
parsing each line:
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz\r\n1,2,3'))
parser.each(fn (result) {
# ...
})
Because parsing a line may produce an error, Parser
yields values of type
Option[Result[Array[String], Error]]
rather than Option[Array[String]]
.
This means you need to handle errors when processing the result values. A
simple way of doing so is to use std.iter.Iter.try_reduce
:
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz\r\n1,2,3'))
let rows = parser
.try_reduce([], fn (rows, result) {
rows.push(try result)
Result.Ok(rows)
})
.get
rows # => [['foo', 'bar', 'baz'], ['1', '2', '3']]
Static methods
new
Show source codeHide source code
fn pub static new(input: I) -> Parser[I] {
Parser(
input: BufferedReader.new(input),
buffer: ByteArray.new,
offset: 0,
separator: Separator.Comma.to_int,
)
}
fn pub static new(input: I: mut) -> Parser[I: mut]
Returns a new Parser
that uses a comma as the column separator.
Examples
import std.csv (Parser)
import std.io (Buffer)
Parser.new(Buffer.new('foo,bar,baz'))
Instance methods
all?
Show source codeHide source code
fn pub mut all?(func: fn (T) -> Bool) -> Bool {
loop {
match self.next {
case Some(v) -> if func.call(v).false? { return false }
case _ -> return true
}
}
}
fn pub mut all?(func: fn (T) -> Bool) -> Bool
Returns true
if the supplied func returns true
for all values in
self
.
This method stops iterating over the values after the first value for which
the closure returns false
.
Examples
Checking if all values in an Iter
match a condition:
[10, 20].iter.all? fn (value) { value.positive? } # => true
[-1, 20].iter.all? fn (value) { value.positive? } # => false
any?
Show source codeHide source code
fn pub mut any?(func: fn (T) -> Bool) -> Bool {
loop {
match self.next {
case Some(v) -> if func.call(v) { return true }
case _ -> return false
}
}
}
fn pub mut any?(func: fn (T) -> Bool) -> Bool
Returns true
if self
contains any value for which the func
argument
returned true
.
This method stops iterating over the values after the first matching value.
Examples
Checking if an Iter
contains a value:
[10, 20, 30].iter.any? fn (value) { value >= 20 } # => true
chain
Show source codeHide source code
fn pub move chain[I: mut + Iter[T]](other: I) -> Stream[T] {
let mut iter_left = true
Stream.new(fn move {
if iter_left {
let item = self.next
if item.some? { return item } else { iter_left = false }
}
other.next
})
}
fn pub move chain[I: mut + Iter[T]](other: I: mut) -> Stream[T]
Join two Iter
objects together, one after another.
Examples
let a = [10, 20, 30]
let b = [40, 50, 60]
a.iter.chain(b.iter).to_array == [10, 20, 30, 40, 50, 60] # => true
chunks
Show source codeHide source code
fn pub move chunks(size: Int) -> Stream[Array[T]] {
Stream.new(fn move {
let chunk = []
while chunk.size < size {
match self.next {
case Some(val) -> chunk.push(val)
case _ -> break
}
}
if chunk.empty? { Option.None } else { Option.Some(chunk) }
})
}
fn pub move chunks(size: Int) -> Stream[Array[T]]
Returns an Iter
that produces chunks of values.
Each chunk is up to the amount specified by the size
argument. If the
number of values can't be evenly divided, the last chunk may contain fewer
than size
elements.
column
Show source codeHide source code
fn pub mut column -> Result[String, Error] {
try read_column
Result.Ok(@buffer.drain_to_string)
}
fn pub mut column -> Result[String, Error]
Parses a single column, returning its value as a String
.
Errors
This method returns an Error
for cases such as:
- Reading data from the underlying stream fails
- No more columns remain
- The column format is invalid
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo'))
parser.column # => Result.Ok('foo')
column?
Show source codeHide source code
fn pub mut column? -> Result[Bool, Error] {
match try peek {
case Some(CR or LF) or None -> Result.Ok(false)
case Some(_) -> Result.Ok(true)
}
}
fn pub mut column? -> Result[Bool, Error]
Returns a boolean indicating if there's data left to parse as a column.
Errors
This method may need to read from the underlying stream to determine if data
is available. If this fails, an Error
is returned.
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo'))
parser.column? # => true
count
Show source codeHide source code
fn pub move count -> Int {
reduce(0, fn (count, _) { count + 1 })
}
fn pub move count -> Int
Returns the number of values remaining in this iterator.
This method advances the iterator to the end.
Examples
[1, 2, 3].iter.count # => 3
each
Show source codeHide source code
fn pub move each(func: fn (T)) {
loop {
match self.next {
case Some(v) -> func.call(v)
case _ -> return
}
}
}
fn pub move each(func: fn (T))
Calls the closure for every value in self
.
Examples
let vals = [10, 20, 30]
let iter = vals.iter
iter.each fn (num) {
num # => 10, 20, 30
}
each_with_index
Show source codeHide source code
fn pub move each_with_index(func: fn (Int, T)) {
let mut idx = 0
each(fn move (value) { func.call(idx := idx + 1, value) })
}
fn pub move each_with_index(func: fn (Int, T))
Calls the closure for every value in self
, passing an index and the value
to the closure.
Examples
let vals = [10, 20, 30]
let iter = vals.iter
iter.each_with_index fn (index, num) {
index # => 0, 1, 2
num # => 10, 20, 30
}
find
Show source codeHide source code
fn pub mut find(func: fn (ref T) -> Bool) -> Option[T] {
loop {
match self.next {
case Some(v) -> if func.call(v) { return Option.Some(v) }
case _ -> return Option.None
}
}
}
fn pub mut find(func: fn (ref T) -> Bool) -> Option[T]
Returns the first value for which the supplied Block
returns true
.
This method will advance the Iter
until either a value is found or we
run out of values.
Examples
Finding the first number greated than 50 in an Array
:
let numbers = [10, 20, 50, 80]
numbers.iter.find fn (number) { number > 50 } # => 80
find_map
Show source codeHide source code
fn pub mut find_map[R](func: fn (T) -> Option[R]) -> Option[R] {
loop {
match self.next {
case Some(v) -> {
match func.call(v) {
case Some(r) -> return Option.Some(r)
case _ -> {}
}
}
case _ -> return Option.None
}
}
}
fn pub mut find_map[R](func: fn (T) -> Option[R]) -> Option[R]
Returns an Iter
that combines find
with map
.
For each value in self
, the supplied closure is called. If the closure
returns a Some
, the value is returned an iteration stops.
Examples
let vals = [10, 20, 30]
let val = vals.into_iter.find_map fn (v) {
if v == 20 { Option.Some(v.to_string) } else { Option.None }
}
val # => Option.Some('20')
headers
Show source codeHide source code
fn pub mut headers -> Result[Map[String, Int], Error] {
let map = Map.new
let mut i = 0
while try column? { map.set(try column, i := i + 1) }
try next_line
Result.Ok(map)
}
fn pub mut headers -> Result[Map[String, Int], Error]
Parses the current line as a list of headers and their corresponding column indexes.
Errors
This method returns an Error
if parsing a column or advancing to the next
line fails.
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar'))
let headers = parser.headers.or_panic('failed to parse the headers')
headers.get('foo') # => 0
headers.get('bar') # => 1
last
Show source codeHide source code
fn pub move last -> Option[T] {
reduce(Option.None, fn (_, val) { Option.Some(val) })
}
fn pub move last -> Option[T]
Returns the last value produced by this iterator.
Examples
[1, 2, 3].iter.last # => Option.Some(3)
map
Show source codeHide source code
fn pub move map[R](func: fn (T) -> R) -> Stream[R] {
Stream.new(fn move { self.next.map(fn (v) { func.call(v) }) })
}
fn pub move map[R](func: fn (T) -> R) -> Stream[R]
Returns an Iter
that transforms values of type T
to values of type
R
.
Examples
Multiplying the numbers in an Array
:
let values = [1, 2, 3]
values.iter.map fn (n) { n * 2 }.to_array # => [2, 4, 6]
next
Show source codeHide source code
fn pub mut next -> Option[Result[Array[String], Error]] {
let mut cols = []
loop {
loop {
match column? {
case Ok(true) -> {
match column {
case Ok(v) -> cols.push(v)
case Error(e) -> return Option.Some(Result.Error(e))
}
}
case Ok(false) -> break
case Error(e) -> return Option.Some(Result.Error(e))
}
}
match next_line {
case Ok(false) if cols.empty? -> return Option.None
case Ok(true) if cols.empty? -> {}
case Ok(_) -> return Option.Some(Result.Ok(cols))
case Error(e) -> return Option.Some(Result.Error(e))
}
}
}
fn pub mut next -> Option[Result[Array[String], Error]]
Parses the current line as an Array
of String
values.
This method skips empty lines rather than returning a
Option.Some(Result.Ok([]))
.
Errors
This method returns an Option.Some(Result.Error(Error))
if parsing the
data on a line fails. Refer to the documentation of Parser.column?
and
Parser.column
for more details.
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('a,b\n\nc,d'))
parser.to_array # => [Result.Ok(['a', 'b']), Result.Ok(['c', 'd'])]
next_line
Show source codeHide source code
fn pub mut next_line -> Result[Bool, Error] {
match try peek {
case Some(CR) -> {
try read_byte
try require_newline
}
case Some(LF) -> try read_byte
case Some(_) -> throw error(ErrorKind.UnexpectedCharacter)
case None -> return Result.Ok(false)
}
match try peek {
case Some(_) -> Result.Ok(true)
case None -> {
try read_byte
Result.Ok(false)
}
}
}
fn pub mut next_line -> Result[Bool, Error]
Advances the cursor to the start of the next line.
This method must be called at the end of each line in order to allow parsing of the next line.
If the next line may contain more data to parse, this method returns
Result.Ok(true)
, otherwise it returns Result.Ok(false)
.
Errors
This method returns an Error
if the current byte is not a cariage return
("\r") or a newline ("\n").
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('\nfoo,bar'))
parser.next_line # => Result.Ok(true)
offset
Show source codeHide source code
fn pub offset -> Int {
@offset
}
fn pub offset -> Int
Returns the current byte offset of the underlying stream.
partition
Show source codeHide source code
fn pub move partition(func: fn (ref T) -> Bool) -> (Array[T], Array[T]) {
reduce(([], []), fn move (acc, val) {
if func.call(ref val) { acc.0.push(val) } else { acc.1.push(val) }
acc
})
}
fn pub move partition(func: fn (ref T) -> Bool) -> (Array[T], Array[T])
Partitions the Iter
into a tuple of two Array
objects.
The first value of the tuple contains all values for which the supplied
func returned true
. The second value contains all values for which the
func returned false
.
Examples
Partitioning an Iter
:
let values = [10, 20, 30, 40, 50]
let pair = values.iter.partition fn (value) { value >= 30 }
pair.0 # => [30, 40, 50]
pair.1 # => [10, 20]
peekable
Show source codeHide source code
fn pub move peekable -> Peekable[T] {
Peekable(iter: self, peeked: Option.None)
}
fn pub move peekable -> Peekable[T]
Returns an iterator that allows looking at the next element of the iterator, without consuming it.
Examples
let vals = [1, 2, 3]
let iter = vals.iter.peekable
iter.peek # => Option.Some(1)
iter.peek # => Option.Some(1)
iter.next # => Option.Some(1)
iter.next # => Option.Some(2)
reduce
Show source codeHide source code
fn pub move reduce[A](accumulator: A, func: fn (A, T) -> A) -> A {
let mut result = accumulator
loop {
match self.next {
case Some(v) -> result = func.call(result, v)
case _ -> return result
}
}
}
fn pub move reduce[A](accumulator: A, func: fn (A, T) -> A) -> A
Combines all values in the iterator into the specified accumulator.
This method takes two arguments:
- An accumulator to combine all values into.
2. A func that accepts two arguments: the accumulator and the current value.
The func returns the accumulator to return for the next value. The return value of this method is the final accumulator.
This method is called inject
or fold
in other languages.
Examples
Summing all integers in an iterator:
let total = [1, 2, 3].iter.reduce(0) fn (total, current) {
total + current
}
total # => 6
At each step of the iteration, the total
and current
func arguments are
set to the following values:
Element | Total | Current | Return value |
---|---|---|---|
0 | 0 | 1 | 1 |
1 | 1 | 2 | 3 |
2 | 3 | 3 | 6 |
For the last element the return value is 6
, so the return value of the
reduce method is also 6
.
select
Show source codeHide source code
fn pub move select(func: fn (ref T) -> Bool) -> Stream[T] {
Stream.new(fn move {
loop {
match self.next {
case Some(v) -> if func.call(v) { return Option.Some(v) }
case _ -> return Option.None
}
}
})
}
fn pub move select(func: fn (ref T) -> Bool) -> Stream[T]
Returns an Iter
that only produces values for which the supplied func
returned true
.
Examples
Selecting only certain values in an Iter
:
[10, 20, 30]
.iter
.select fn (value) { value > 10 }
.to_array # => [20, 30]
select_map
Show source codeHide source code
fn pub move select_map[R](func: fn (T) -> Option[R]) -> Stream[R] {
Stream.new(fn move {
loop {
match self.next {
case Some(v) -> {
match func.call(v) {
case Some(r) -> return Option.Some(r)
case _ -> next
}
}
case _ -> return Option.None
}
}
})
}
fn pub move select_map[R](func: fn (T) -> Option[R]) -> Stream[R]
Returns an Iter
that both filters and maps at the same time.
The new iterator yields the values for which the provided func returned a
Some
value.
Examples
let vals = [Option.Some(10), Option.None, Option.som(30)]
let iter = vals.into_iter.select_map fn (opt) { opt }
iter.next # => Option.Some(10)
iter.next # => Option.Some(30)
iter.next # => Option.None
separator=
Show source codeHide source code
fn pub mut separator=(separator: ref Separator) {
@separator = separator.to_int
}
fn pub mut separator=(separator: ref Separator)
Changes the separator to use for separating columns to the given
Separator
.
Examples
import std.csv (Separator, Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz'))
parser.separator = Separator.Tab
skip
Show source codeHide source code
fn pub move skip(amount: Int) -> Stream[T] {
let mut pending = amount
Stream.new(fn move {
while pending > 0 {
match self.next {
case Some(_) -> pending -= 1
case _ -> break
}
}
self.next
})
}
fn pub move skip(amount: Int) -> Stream[T]
Returns a new iterator that skips the first amount
values yielded, then
yields any remaining values.
The first time next
is called on the returned iterator, the iterator is
advanced amount
times. The new iterator yields a None
if there are fewer
than amount
items in the iterator.
Examples
[1, 2, 3].into_iter.skip(1).to_array # => [2, 3]
[1].into_iter.skip(10).to_array # => []
skip_column
Show source codeHide source code
fn pub mut skip_column -> Result[Nil, Error] {
try read_column
@buffer.clear
Result.Ok(nil)
}
fn pub mut skip_column -> Result[Nil, Error]
Parses a single column and discards its value.
Unlike Parser.column
, this method doesn't allocate a String
, making it a
little more efficient when skipping over large or many columns.
Errors
This method returns an Error
for cases such as:
- Reading data from the underlying stream fails
- No more columns remain
- The column format is invalid
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar'))
parser.skip_column # => Result.Ok(nil)
parser.column # => Result.Ok('bar')
skip_columns
Show source codeHide source code
fn pub mut skip_columns(amount: Int) -> Result[Nil, Error] {
let mut i = 0
while (i := i + 1) < amount { try skip_column }
Result.Ok(nil)
}
fn pub mut skip_columns(amount: Int) -> Result[Nil, Error]
Parses and discards the values of multiple columns.
This method calls Parser.skip_column
in a loop amount
times, so refer to
its documentation for more details.
Errors
This method returns an Error
if skipping one of the columns fails. Refer
to the documentation of Parser.skip_column
for more details.
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar,baz'))
parser.skip_columns(2) # => Result.Ok(nil)
parser.column # => Result.Ok('baz')
skip_line
Show source codeHide source code
fn pub mut skip_line -> Result[Nil, Error] {
loop {
match try read_byte {
case Some(LF) -> break
case Some(_) -> {}
case None -> break
}
}
Result.Ok(nil)
}
fn pub mut skip_line -> Result[Nil, Error]
Skips all (remaining) data on the current line and moves the cursor to the start of the next line.
Errors
This method returns an Error
if the data can't be read from the underlying
stream.
Examples
import std.csv (Parser)
import std.io (Buffer)
let parser = Parser.new(Buffer.new('foo,bar'))
parser.skip_line # => Result.Ok(nil)
skip_while
Show source codeHide source code
fn pub move skip_while(func: fn (ref T) -> Bool) -> Stream[T] {
let mut skip = true
Stream.new(fn move {
while skip {
match self.next {
case Some(v) if func.call(v) -> {}
case Some(v) -> {
skip = false
return Option.Some(v)
}
case _ -> return Option.None
}
}
self.next
})
}
fn pub move skip_while(func: fn (ref T) -> Bool) -> Stream[T]
Returns a new iterator that skips values for which func
returns true
.
Once func
returns false
, the remaining values in the iterator are
yielded, and func
is no longer called.
Examples
[1, 2, 3].into_iter.skip_while fn (v) { v <= 2 }.to_array # => [3]
take
Show source codeHide source code
fn pub move take(amount: Int) -> Stream[T] {
let mut pending = amount
Stream.new(fn move {
if (pending := pending - 1) > 0 { self.next } else { Option.None }
})
}
fn pub move take(amount: Int) -> Stream[T]
Returns a new iterator that yields the first amount
items, or fewer if
amount
is greater than the number of values in the iterator.
Examples
[1, 2, 3].into_iter.take(2).to_array # => [1, 2]
[1].into_iter.take(2).to_array # => [1]
take_while
Show source codeHide source code
fn pub move take_while(func: fn (ref T) -> Bool) -> Stream[T] {
let mut take = true
Stream.new(fn move {
if take {
match self.next {
case Some(v) if func.call(v) -> Option.Some(v)
case Some(_) -> {
take = false
Option.None
}
case _ -> Option.None
}
} else {
Option.None
}
})
}
fn pub move take_while(func: fn (ref T) -> Bool) -> Stream[T]
Returns a new iterator that yields values for which func
returns true
.
When func
returns false
, any remaining values in the iterator are
ignored and the iterator is no longer advanced.
Examples
[1, 2, 3].into_iter.take_while fn (v) { v <= 2 }.to_array # => [1, 2]
to_array
Show source codeHide source code
fn pub move to_array -> Array[T] {
reduce([], fn (values, value) {
values.push(value)
values
})
}
fn pub move to_array -> Array[T]
Collects all values in the Iter
in an Array
.
This method will advance the iterator to the end.
Examples
Transforming an Iter
back into an Array
:
[1, 2, 3].iter.to_array # => [1, 2, 3]
try_each
Show source codeHide source code
fn pub mut try_each[E](func: fn (T) -> Result[Nil, E]) -> Result[Nil, E] {
loop {
match self.next {
case Some(v) -> try func.call(v)
case _ -> return Result.Ok(nil)
}
}
}
fn pub mut try_each[E](func: fn (T) -> Result[Nil, E]) -> Result[Nil, E]
Calls the closure for every value in self
, stopping at the first Error
returned by the closure.
Examples
let res = [10, 0, 30].into_iter.try_each fn (val) {
if val > 0 { Result.Ok(nil) } else { Result.Error('test') }
}
res # => Result.Error('test')
try_reduce
Show source codeHide source code
fn pub mut try_reduce[A, E](
accumulator: A,
func: fn (A, T) -> Result[A, E],
) -> Result[A, E] {
let mut result = accumulator
loop {
match self.next {
case Some(v) -> result = try func.call(result, v)
case _ -> return Result.Ok(result)
}
}
}
fn pub mut try_reduce[A, E](accumulator: A, func: fn (A, T) -> Result[A, E]) -> Result[A, E]
Combines all values in the iterator into the specified accumulator, stopping
at the first Error
that is encountered.
This method is similar to Iter.reduce
, except the given closure is
expected to return a Result
wrapping the accumulator. If the return value
is an Ok
, iteration continues. If the return value is an Error
,
iteration stops and the Error
is returned.
Examples
let iter = [Result.Ok(1), Result.Error('test'), Result.Ok(2)].into_iter
let result = iter.try_reduce(0) fn (acc, val) {
match val {
case Ok(val) -> Result.Ok(acc + val)
case err -> err
}
}
result # => Result.Error('test')
with_index
Show source codeHide source code
fn pub move with_index -> Stream[(Int, T)] {
let mut index = 0
map(fn move (val) { (index := index + 1, val) })
}
fn pub move with_index -> Stream[(Int, T)]
Returns an Iter
that yields a tuple of the index and the iterator value.
Examples
let iter = [10, 20].into_iter.with_index
iter.next # => Option.Some((0, 10))
iter.next # => Option.Some((1, 20))
zip
Show source codeHide source code
fn pub move zip[U, I: mut + Iter[U]](other: I) -> Stream[(T, U)] {
Stream.new(fn move { self.next.zip(other.next) })
}
fn pub move zip[U, I: mut + Iter[U]](other: I: mut) -> Stream[(T, U)]
Zips two Iter
objects together, producing a new Iter
that produces a
tuple containing the values of both Iter
objects.
The returned Iter
stops iterating when one of the two zipped Iter
objects runs out of values.
Examples
Zipping two Iter
objects together:
let a = [10, 20, 30]
let b = [40, 50, 60]
let zip = a.iter.zip(b.iter)
zip.next # => (10, 40)
Zipping two Iter
objects with a different number of values:
let a = [10, 20, 30]
let b = [40, 50]
let zip = a.iter.zip(b.iter)
zip.next # => (10, 40)
Implemented traits
Iter
impl Iter[Result[Array[String], Error]] for Parser