55
66/// A newline terminator position.
77#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
8+ #[ must_use]
89pub struct RowEnd {
910 /// Byte position of terminator start (\n or \r in \r\n).
1011 pub pos : u32 ,
@@ -14,6 +15,7 @@ pub struct RowEnd {
1415
1516/// Structural index: positions of all unquoted separators and row endings.
1617#[ derive( Debug ) ]
18+ #[ must_use]
1719pub struct StructuralIndex {
1820 /// Positions of unquoted field separators (commas, tabs, etc.).
1921 pub field_seps : Vec < u32 > ,
@@ -58,6 +60,7 @@ impl StructuralIndex {
5860
5961 /// Number of rows.
6062 #[ inline]
63+ #[ must_use]
6164 pub fn row_count ( & self ) -> usize {
6265 let n = self . row_ends . len ( ) ;
6366 // If there's content after the last row_end (no trailing newline), there's one more row.
@@ -130,6 +133,7 @@ impl<'a> Iterator for RowIter<'a> {
130133 let start = self . pos ;
131134 let end = self . index . input_len ;
132135 self . pos = end;
136+ self . row_idx += 1 ;
133137 Some ( ( start, end, end) )
134138 } else {
135139 None
@@ -144,6 +148,8 @@ impl<'a> Iterator for RowIter<'a> {
144148 }
145149}
146150
151+ impl ExactSizeIterator for RowIter < ' _ > { }
152+
147153/// A single row from the cursor-based iterator, with its field bounds.
148154pub struct Row < ' a > {
149155 pub start : u32 ,
@@ -177,6 +183,7 @@ impl<'a> Iterator for RowFieldIter<'a> {
177183 let start = self . pos ;
178184 let end = self . index . input_len ;
179185 self . pos = end;
186+ self . row_idx += 1 ;
180187 ( start, end)
181188 } else {
182189 return None ;
@@ -210,6 +217,8 @@ impl<'a> Iterator for RowFieldIter<'a> {
210217 }
211218}
212219
220+ impl ExactSizeIterator for RowFieldIter < ' _ > { }
221+
213222/// Iterator over fields in a single row.
214223pub struct FieldIter < ' a > {
215224 seps : & ' a [ u32 ] ,
@@ -249,11 +258,16 @@ impl<'a> Iterator for FieldIter<'a> {
249258
250259 #[ inline]
251260 fn size_hint ( & self ) -> ( usize , Option < usize > ) {
261+ if self . done {
262+ return ( 0 , Some ( 0 ) ) ;
263+ }
252264 let remaining = ( self . seps . len ( ) + 1 ) . saturating_sub ( self . idx ) ;
253265 ( remaining, Some ( remaining) )
254266 }
255267}
256268
269+ impl ExactSizeIterator for FieldIter < ' _ > { }
270+
257271#[ cfg( test) ]
258272mod tests {
259273 use super :: * ;
@@ -373,4 +387,74 @@ mod tests {
373387 assert_eq ! ( cursor[ 0 ] , vec![ ( 0 , 1 ) , ( 2 , 3 ) ] ) ; // a,b
374388 assert_eq ! ( cursor[ 1 ] , vec![ ( 4 , 5 ) ] ) ; // c
375389 }
390+
391+ // --- ExactSizeIterator correctness tests ---
392+
393+ #[ test]
394+ fn row_iter_exact_size_with_trailing_row ( ) {
395+ // "a\nb" — 2 rows, second has no trailing newline
396+ let idx = make_index ( vec ! [ ] , vec ! [ RowEnd { pos: 1 , len: 1 } ] , 3 ) ;
397+ let mut iter = idx. rows ( ) ;
398+
399+ assert_eq ! ( iter. len( ) , 2 ) ;
400+ let _ = iter. next ( ) ; // consume row 1
401+ assert_eq ! ( iter. len( ) , 1 ) ;
402+ let _ = iter. next ( ) ; // consume trailing row
403+ assert_eq ! ( iter. len( ) , 0 ) ;
404+ assert ! ( iter. next( ) . is_none( ) ) ;
405+ }
406+
407+ #[ test]
408+ fn row_iter_exact_size_no_trailing_row ( ) {
409+ // "a\n" — 1 row with trailing newline
410+ let idx = make_index ( vec ! [ ] , vec ! [ RowEnd { pos: 1 , len: 1 } ] , 2 ) ;
411+ let mut iter = idx. rows ( ) ;
412+
413+ assert_eq ! ( iter. len( ) , 1 ) ;
414+ let _ = iter. next ( ) ;
415+ assert_eq ! ( iter. len( ) , 0 ) ;
416+ assert ! ( iter. next( ) . is_none( ) ) ;
417+ }
418+
419+ #[ test]
420+ fn row_field_iter_exact_size_with_trailing_row ( ) {
421+ // "a\nb" — 2 rows, second has no trailing newline
422+ let idx = make_index ( vec ! [ ] , vec ! [ RowEnd { pos: 1 , len: 1 } ] , 3 ) ;
423+ let mut iter = idx. rows_with_fields ( ) ;
424+
425+ assert_eq ! ( iter. len( ) , 2 ) ;
426+ let _ = iter. next ( ) ;
427+ assert_eq ! ( iter. len( ) , 1 ) ;
428+ let _ = iter. next ( ) ; // trailing row
429+ assert_eq ! ( iter. len( ) , 0 ) ;
430+ assert ! ( iter. next( ) . is_none( ) ) ;
431+ }
432+
433+ #[ test]
434+ fn field_iter_exact_size_single_field ( ) {
435+ // Row "abc" — 1 field, no separators
436+ let idx = make_index ( vec ! [ ] , vec ! [ RowEnd { pos: 3 , len: 1 } ] , 4 ) ;
437+ let mut fields = idx. fields_in_row ( 0 , 3 ) ;
438+
439+ assert_eq ! ( fields. len( ) , 1 ) ;
440+ let _ = fields. next ( ) ; // consume the only field
441+ assert_eq ! ( fields. len( ) , 0 ) ;
442+ assert ! ( fields. next( ) . is_none( ) ) ;
443+ }
444+
445+ #[ test]
446+ fn field_iter_exact_size_multiple_fields ( ) {
447+ // Row "a,b,c" — 3 fields, seps at 1 and 3
448+ let idx = make_index ( vec ! [ 1 , 3 ] , vec ! [ RowEnd { pos: 5 , len: 1 } ] , 6 ) ;
449+ let mut fields = idx. fields_in_row ( 0 , 5 ) ;
450+
451+ assert_eq ! ( fields. len( ) , 3 ) ;
452+ let _ = fields. next ( ) ; // field "a"
453+ assert_eq ! ( fields. len( ) , 2 ) ;
454+ let _ = fields. next ( ) ; // field "b"
455+ assert_eq ! ( fields. len( ) , 1 ) ;
456+ let _ = fields. next ( ) ; // field "c" (last, sets done=true)
457+ assert_eq ! ( fields. len( ) , 0 ) ;
458+ assert ! ( fields. next( ) . is_none( ) ) ;
459+ }
376460}
0 commit comments