You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
However, the implementation has started to grow a second CSV parser alongside csv-core. Features that are still missing—multiline records across iterator items, strict, complete escape handling, Unicode dialect characters, empty-line behavior, and the remaining quote modes—will require more persistent parser state and more CPython-specific semantics.
At this point, it seems useful to compare the contracts of CPython, csv-core, and RustPython before the custom parser grows further.
This issue:
summarizes the current implementation differences;
explains why an unchanged csv-core wrapper cannot provide full CPython compatibility without duplicating much of the grammar;
compares the main long-term architecture options without selecting one.
My initial preference is to preserve the performance model of csv-core, potentially through a RustPython-specific adaptation or fork. However, Unicode and strict error semantics may make that a much larger redesign than it first appears, so this issue intentionally leaves the final choice open.
quoting and lineterminator from a dialect were ignored. This shows that dialect resolution should be centralized before configuring reader/writer backends.
Replaces unsafe delimiter splitting with a quote-aware scanner and expands the custom path to QUOTE_NOTNULL and QUOTE_STRINGS.
The issues are individually valid and can continue to receive narrow fixes. The architectural concern is that each fix currently has to decide independently whether its semantics belong in FormatOptions, preprocessing, csv-core, a custom parser, or post-processing.
Scope
The primary subject is the reader parser architecture.
Dialect representation and validation are included only where they constrain the parser architecture, especially Unicode dialect characters and the conversion to csv-core types.
Writer implementation is not proposed to share the reader state machine. The writer issues above are included as evidence that the current dialect abstraction also affects writer compatibility.
This issue is not intended to block small compatibility fixes. Its purpose is to make the long-term cost of each parser direction visible before larger changes accumulate around the current hybrid structure.
Current implementation layers
At a high level, RustPython currently has these layers:
Python arguments / dialect object
|
v
FormatOptions + PyDialect
|
+-----------------------+
| |
v v
csv_core::Reader custom Rust parser paths
(common quote modes) (currently selected cases)
| |
+-----------+-----------+
|
v
Python field conversion
This layering has allowed incremental progress, but parser semantics are now distributed across:
argument parsing and dialect conversion;
csv-core::ReaderBuilder;
raw-input preprocessing such as skipinitialspace;
custom quote-mode paths;
post-processing for QUOTE_NONNUMERIC, QUOTE_STRINGS, and QUOTE_NOTNULL;
outer iterator lifecycle and line_num.
The same input may be quote-scanned by RustPython and then parsed again by csv-core.
CPython stores the following as Python Unicode or Unicode code points:
delimiter: one Unicode character;
quotechar: one Unicode character or None;
escapechar: one Unicode character or None;
lineterminator: an arbitrary string.
It validates the dialect once when constructing the internal dialect object. Validation includes:
exact accepted types;
exactly one character where required;
valid quoting enum values;
requiring quotechar when quoting is enabled;
conflicts among delimiter, quotechar, escapechar, and lineterminator;
special restrictions involving CR, LF, spaces, and skipinitialspace.
The reader does not use lineterminator to recognize input records. CPython's reader recognizes CR and LF directly. lineterminator is primarily a writer setting.
Reader state machine
CPython's reader uses a persistent nine-state parser:
RustPython declares csv-core = "0.1.11" as a caret dependency; the current lockfile resolves 0.1.13.
Performance model
csv-core is intentionally optimized around byte input:
parser syntax characters are u8;
input is expected to be at least ASCII-compatible;
the implementation contains an explicit NFA and generates a compact DFA transition table from it;
parser configuration is compiled into the transition table;
read_record writes unescaped field data into caller-provided output buffers and writes field end offsets into another buffer;
the core parser does not allocate.
This is a strong implementation for its intended contract.
Semantic differences from CPython
csv-core deliberately prefers a parse over rejecting malformed input.
Relevant differences include:
special dialect characters are bytes, not arbitrary Python Unicode characters;
an empty byte slice is the parser's true EOF signal;
there is no Python iterator-item boundary event;
parsing never returns a malformed-CSV error;
open quoted fields are finalized at EOF instead of producing CPython's strict error;
escape handling is implemented inside quoted fields, not in unquoted fields;
skipinitialspace is not part of the reader state machine;
quoted/unquoted field provenance is not returned;
empty input-line behavior differs;
internal NFA/DFA state is private, so an adapter cannot inspect whether InputEmpty occurred in an unquoted field, quoted field, escape state, or closing-quote state;
the reader's Terminator abstraction is byte-oriented and differs from CPython's input model.
These are not necessarily bugs in csv-core; they reflect a different public contract and design philosophy.
“one Python character” is currently implemented as “exactly one encoded byte” in several paths;
non-ASCII delimiter, quotechar, or escapechar cannot be represented correctly;
a full Python lineterminator string cannot be represented;
reader and writer configuration are constrained by csv-core types before the Python dialect contract has been fully resolved;
validation and dialect override behavior are spread across TryFromObject, FromArgs, result, to_reader, and to_writer;
invalid-type behavior can differ depending on whether a setting comes from a dialect object or a keyword argument, as seen in csv: reader allows escapechar=1 #8284.
Reader lifecycle
The current outer Reader.next() fetches one Python iterator item.
The csv-core path may call read_record repeatedly to resize buffers or consume the remainder of that same item, but it does not fetch another Python item for the same record. When the current byte slice is exhausted, passing an empty slice to csv-core also acts as csv-core EOF and can finalize an open quote.
This differs from CPython, where:
iterator-item end is a virtual EOL event;
true EOF is a separate event;
one record may consume multiple iterator items.
Custom parser growth
#8260 added a small parser for QUOTE_NONE + escapechar, because an escaped delimiter in an unquoted field cannot be expressed through the current csv-core reader API.
#8304 generalizes this into quote-aware scanning and uses custom parsing for:
QUOTE_NONE with an escape character;
QUOTE_NOTNULL;
QUOTE_STRINGS.
It also uses the scanner as a preprocessing pass for skipinitialspace before the common csv-core path.
This is the point where the custom implementation becomes more than a local exception:
it recognizes field starts;
it tracks quoted/unquoted state;
it handles quote closing;
it handles doubled quotes;
it handles escapes;
it recognizes delimiters and record terminators;
it records quoted-field provenance.
Those are the core responsibilities of a CSV parser.
The custom path still has local, per-item state, so extending it to multiline records and strict EOF behavior will require moving parser state into the reader object and changing the outer iterator lifecycle.
Performance uncertainty
The common path retains csv-core's DFA, but skipinitialspace in #8304 first quote-scans and copies the complete input before csv-core parses it again.
The custom path allocates per-field buffers instead of using csv-core's contiguous output buffer and field-end offsets.
This does not prove that the end-to-end implementation is slow. Python object creation may dominate total cost. It does mean that the performance benefit of keeping csv-core should be measured at the RustPython API level rather than assumed from csv-core microbenchmarks.
Compatibility matrix
The “RustPython proposed” column refers to the architecture direction exposed by #8304, not necessarily merged main.
Capability
CPython 3.14
csv-core
RustPython current / proposed
Input alphabet
Python Unicode code points
bytes
Python strings converted to bytes
Delimiter / quote / escape
one Unicode character
one u8
one u8
Writer lineterminator
arbitrary string
CRLF or one byte terminator
csv_core::Terminator
Reader record termination
CR/LF; ignores dialect lineterminator
configured byte terminator
configured through csv-core
Iterator item boundary
explicit virtual EOL
none
currently treated like parser EOF
True iterator EOF
separate from item end
empty input slice
not cleanly separated for the core parser
Record spans iterator items
yes
yes, if caller supplies more slices without EOF
no in current outer lifecycle
Escape in quoted field
yes
yes
common path and custom scanner
Escape in unquoted field
yes
no
custom path for selected modes
skipinitialspace
parser state transition
unsupported
preprocessing scanner
Strict malformed-CSV errors
yes
intentionally no
dialect flag exists; parser semantics incomplete
Quoted/unquoted provenance
yes
not returned
custom path for selected modes
QUOTE_STRINGS conversion
yes
outside its scope
partial
QUOTE_NOTNULL conversion
yes
outside its scope
custom path
Empty line semantics
returns an empty record according to iterator/input state
empty records are generally skipped
special-cased outside the core parser
Allocation strategy
growable field buffer + Python objects
caller-provided contiguous buffers
core buffers on common path; per-field vectors on custom path
Malformed input philosophy
compatible permissive mode plus strict mode
always prefer a parse
split between core and RustPython checks
Why an unchanged thin wrapper is insufficient
Some missing behavior can be implemented around csv-core:
dialect validation;
Python object conversion;
line counting;
some empty-line handling;
quote provenance, if a separate scanner tracks it.
The difficult cases are those where csv-core consumes syntax before the wrapper can reinterpret it.
Unquoted escape
In CPython, escape is recognized in both START_FIELD and IN_FIELD.
If an escaped byte is a delimiter, CR, LF, quote, or another special byte, unchanged csv-core will still interpret that byte according to its grammar. A wrapper would need to:
replace escaped syntax with collision-free placeholders and restore it later;
bypass csv-core output for parts of a field;
or dispatch those dialects to another parser.
All of these add a second grammar and difficult state synchronization.
Strict parsing
csv-core does not expose enough internal state to distinguish:
a normal unquoted field;
an open quoted field;
an escape-pending state;
the state immediately after a closing quote.
A wrapper can run a shadow CPython-like FSM over the raw input, but then the wrapper is implementing most of the parser grammar solely to validate another parser.
Iterator boundaries
ReadRecordResult::InputEmpty does not tell the caller whether more Python iterator items should be fetched as part of the same record. An empty input slice means true EOF to csv-core, while CPython requires a distinct item-boundary event.
A shadow state machine can decide whether to fetch another item, but again this duplicates the parser state.
Therefore, a complete wrapper is technically possible, but it is no longer a thin adapter and may be harder to reason about than a single compatibility parser.
Architecture options
Option A: Continue the current hybrid approach
Keep csv-core for common cases and add custom paths or preprocessing for each unsupported feature.
Pros
smallest incremental changes;
existing working paths remain stable;
preserves csv-core throughput for inputs that avoid preprocessing and custom dispatch;
individual contributors can fix isolated compatibility failures.
Cons
quote modes and dialect combinations can select different parsers;
parser semantics are distributed across preprocessing, core parsing, custom parsing, and conversion;
multiline and strict handling require persistent state in every applicable path;
each new feature increases the cross-product of dialect options and parser paths;
duplicate grammars can diverge on malformed input and boundaries;
performance can include double scanning and extra allocation;
Unicode still requires a separate design.
Assessment
Reasonable for narrow fixes, but not a good long-term architecture if CPython compatibility remains the goal.
Option B: Keep unchanged csv-core plus a comprehensive wrapper/shadow FSM
Use csv-core for output generation while a RustPython-owned FSM tracks CPython state, validates strict mode, handles iterator boundaries, and records provenance.
Pros
continues using the current dependency without a fork;
retains the optimized DFA and contiguous output buffers;
may preserve good performance for ASCII data;
can be introduced incrementally around the current backend.
Cons
the shadow FSM duplicates most of the CSV grammar;
two parsers must consume exactly the same logical input and remain synchronized;
unquoted escape cannot be represented cleanly through the existing public API;
placeholder or output-bypass schemes complicate field offsets and correctness;
private csv-core state prevents direct synchronization checks;
debugging requires understanding both parsers;
Unicode remains outside the core backend.
Assessment
Possible, but the complexity is hidden rather than removed. A prototype should demonstrate that it is materially simpler or faster than an independent parser before choosing this option.
Option C: Implement one RustPython-owned CPython-compatible Unicode parser
Model the CPython state machine directly in Rust, operating on a Python-character representation rather than u8.
Pros
one source of truth for quote, escape, delimiter, strict, multiline, empty-line, and boundary semantics;
parser states map directly to CPython tests and source;
arbitrary Python delimiter, quotechar, and escapechar become representable;
virtual item end and true EOF can be first-class events;
quote provenance and conversion metadata are natural outputs;
no backend dispatch by quote style;
easier differential testing against CPython.
Cons
RustPython owns parser correctness and maintenance;
initial implementation and review scope are larger;
careful buffer design is needed to avoid unnecessary allocation;
the existing csv-core reader performance path may be lost;
performance needs to be recovered through profiling and optimization;
CPython's behavior includes subtle permissive cases, not just the obvious nine states.
Assessment
The clearest route to compatibility and maintainability, but potentially the largest short-term implementation and performance risk.
Option D: Adapt, fork, or vendor csv-core for RustPython
Add the missing states and metadata to the csv-core NFA/DFA design:
skipinitialspace;
unquoted escape states;
iterator-item boundary versus true EOF;
strict error transitions;
quoted/unquoted provenance;
CPython empty-line behavior;
Unicode-capable dialect syntax.
The practical form could be a RustPython-specific fork, vendored code, or a substantial adapter-owned variant. An upstream contribution would only be realistic where the required changes are sufficiently general; it should not be assumed as the primary path.
Pros
preserves the NFA-to-DFA approach and configuration-specific transition tables;
can retain contiguous caller-provided output buffers;
unsupported semantics become real parser transitions instead of preprocessing;
one parser can potentially serve all quote modes;
offers the strongest chance of combining compatibility with the current performance model.
Cons
csv-core intentionally uses bytes and intentionally never reports malformed-CSV errors; CPython compatibility conflicts with both design choices;
Unicode support is not a small type substitution because APIs, input indexing, output buffers, and equivalence-class construction are byte-oriented;
a RustPython-specific fork introduces dependency maintenance and rebase burden;
the result could become a mostly independent parser while retaining complex DFA machinery;
implementation and review cost are high;
the required work may exceed the value of preserving the existing backend.
Important note
Unicode does not make a DFA impossible. A parser can classify each Python character into a small set such as delimiter, quote, escape, space, CR, LF, item-end, and other. The difficulty is that the current csv-core API and buffer model are byte-based, so reaching that design requires a substantial refactor.
Assessment
Potentially the best performance/compatibility combination, but also the highest architectural and maintenance ambition. Its feasibility is uncertain enough that it should remain an option rather than the conclusion of this issue.
Option E: Use two explicit backends
Use a fast ASCII backend when a dialect is fully representable and a compatibility backend otherwise.
For example:
ResolvedDialect
|
+-- Fast backend: csv-core or an extended csv-core
|
+-- Compatibility backend: RustPython Unicode FSM
Pros
preserves optimized performance for common ASCII CSV;
provides a direct path to full Unicode and CPython semantics;
makes fallback conditions explicit instead of growing quote-mode-specific exceptions;
backend performance can evolve independently.
Cons
two parser implementations must remain semantically equivalent in their overlapping domain;
differential testing becomes mandatory;
dispatch criteria can become another source of bugs;
malformed-input behavior may drift;
maintenance and code size are higher than a unified parser;
fast-path wins may be small after Python object conversion.
Assessment
A practical compromise if benchmarks show a meaningful end-to-end benefit. It should use a shared lifecycle and output contract so that only the transition engine differs.
Note on compatibility fixes during this discussion
Narrow fixes with focused tests can still be useful. The concern here is not the existence of custom code by itself, but whether larger additions continue to deepen several partially overlapping parser paths without an agreed long-term direction.
Discussion goal
The main questions for this issue are:
Is the current hybrid structure a sustainable long-term architecture, or should it be treated as transitional?
Among Options B–E, which direction best balances CPython compatibility, implementation complexity, maintenance cost, and the performance value of csv-core?
This issue does not need to choose an implementation plan immediately. Reaching a shared understanding of the architectural trade-offs would already provide guidance for subsequent CSV work.
Appendix: backend-independent constraints
The following points are relevant to any replacement or dual-backend design, but they are secondary to the architecture comparison above.
The internal dialect model must be able to represent CPython's Unicode delimiter, quotechar, and escapechar before converting to backend-specific types.
A Python iterator-item boundary must remain distinct from true input EOF.
Parser state must be able to persist across iterator items for multiline records and boundary-sensitive escape handling.
The parser must retain quoted/unquoted provenance for QUOTE_NOTNULL, QUOTE_STRINGS, and numeric conversion.
Syntax parsing and Python object conversion should remain distinguishable responsibilities.
skipinitialspace is safer as a parser transition than as a preprocessing pass that rewrites the input before another parser sees it.
These constraints do not imply one specific backend. They describe compatibility requirements that Options C, D, and E would need to address, and that make Option B difficult to keep thin.
Summary
Recent CSV compatibility work suggests that RustPython is reaching the architectural boundary of the current
csv-coreadapter.This is not intended to argue that the recent incremental fixes were wrong. In particular:
QUOTE_NONEwithescapechar.skipinitialspacepreprocessing and needs quote provenance forQUOTE_NOTNULL/QUOTE_STRINGS.However, the implementation has started to grow a second CSV parser alongside
csv-core. Features that are still missing—multiline records across iterator items,strict, complete escape handling, Unicode dialect characters, empty-line behavior, and the remaining quote modes—will require more persistent parser state and more CPython-specific semantics.At this point, it seems useful to compare the contracts of CPython,
csv-core, and RustPython before the custom parser grows further.This issue:
csv-corewrapper cannot provide full CPython compatibility without duplicating much of the grammar;My initial preference is to preserve the performance model of
csv-core, potentially through a RustPython-specific adaptation or fork. However, Unicode and strict error semantics may make that a much larger redesign than it first appears, so this issue intentionally leaves the final choice open.Related work
quotingandlineterminatorfrom a dialect were ignored. This shows that dialect resolution should be centralized before configuring reader/writer backends.escapecharintocsv-coreand added a customQUOTE_NONEparser becausecsv-coredoes not apply escape handling to unquoted fields.escapecharvalues that CPython rejects.QUOTE_MINIMALeven when a customlineterminatordoes not contain CR/LF.QUOTE_NOTNULLandQUOTE_STRINGS.The issues are individually valid and can continue to receive narrow fixes. The architectural concern is that each fix currently has to decide independently whether its semantics belong in
FormatOptions, preprocessing,csv-core, a custom parser, or post-processing.Scope
The primary subject is the reader parser architecture.
Dialect representation and validation are included only where they constrain the parser architecture, especially Unicode dialect characters and the conversion to
csv-coretypes.Writer implementation is not proposed to share the reader state machine. The writer issues above are included as evidence that the current dialect abstraction also affects writer compatibility.
This issue is not intended to block small compatibility fixes. Its purpose is to make the long-term cost of each parser direction visible before larger changes accumulate around the current hybrid structure.
Current implementation layers
At a high level, RustPython currently has these layers:
This layering has allowed incremental progress, but parser semantics are now distributed across:
csv-core::ReaderBuilder;skipinitialspace;QUOTE_NONNUMERIC,QUOTE_STRINGS, andQUOTE_NOTNULL;line_num.The same input may be quote-scanned by RustPython and then parsed again by
csv-core.1. CPython 3.14 contract
Reference:
Modules/_csv.cat v3.14.6Lib/test/test_csv.pyat v3.14.6Dialect representation
CPython stores the following as Python Unicode or Unicode code points:
delimiter: one Unicode character;quotechar: one Unicode character orNone;escapechar: one Unicode character orNone;lineterminator: an arbitrary string.It validates the dialect once when constructing the internal dialect object. Validation includes:
quotecharwhen quoting is enabled;skipinitialspace.The reader does not use
lineterminatorto recognize input records. CPython's reader recognizes CR and LF directly.lineterminatoris primarily a writer setting.Reader state machine
CPython's reader uses a persistent nine-state parser:
The parser consumes Python Unicode code points, not encoded bytes.
Important properties:
reader.__next__()may pull multiple strings from the input iterator until a complete CSV record is produced.EOLsentinel.EOLmeans “this iterator item ended”; it is not true iterator EOF and is not identical to CR or LF.StopIteration.strict=Truecan reject invalid characters after a closing quote and unexpected true EOF.QUOTE_NOTNULL,QUOTE_STRINGS, and numeric conversion.line_numcounts successfully obtained input iterator items, not returned CSV records.Output conversion
Parsing and Python-level conversion are related but separable:
Noneconversion;float()forQUOTE_NONNUMERICandQUOTE_STRINGS;START_RECORDagain.2.
csv-corecontractReferences:
csv-corereader documentationcsv-core/src/reader.rsRustPython declares
csv-core = "0.1.11"as a caret dependency; the current lockfile resolves0.1.13.Performance model
csv-coreis intentionally optimized around byte input:u8;read_recordwrites unescaped field data into caller-provided output buffers and writes field end offsets into another buffer;This is a strong implementation for its intended contract.
Semantic differences from CPython
csv-coredeliberately prefers a parse over rejecting malformed input.Relevant differences include:
skipinitialspaceis not part of the reader state machine;InputEmptyoccurred in an unquoted field, quoted field, escape state, or closing-quote state;Terminatorabstraction is byte-oriented and differs from CPython's input model.These are not necessarily bugs in
csv-core; they reflect a different public contract and design philosophy.3. RustPython's current contract
Reference:
crates/stdlib/src/csv.rsDialect model
RustPython currently stores:
Consequences:
lineterminatorstring cannot be represented;csv-coretypes before the Python dialect contract has been fully resolved;TryFromObject,FromArgs,result,to_reader, andto_writer;Reader lifecycle
The current outer
Reader.next()fetches one Python iterator item.The
csv-corepath may callread_recordrepeatedly to resize buffers or consume the remainder of that same item, but it does not fetch another Python item for the same record. When the current byte slice is exhausted, passing an empty slice tocsv-corealso acts ascsv-coreEOF and can finalize an open quote.This differs from CPython, where:
EOLevent;Custom parser growth
#8260 added a small parser for
QUOTE_NONE + escapechar, because an escaped delimiter in an unquoted field cannot be expressed through the currentcsv-corereader API.#8304 generalizes this into quote-aware scanning and uses custom parsing for:
QUOTE_NONEwith an escape character;QUOTE_NOTNULL;QUOTE_STRINGS.It also uses the scanner as a preprocessing pass for
skipinitialspacebefore the commoncsv-corepath.This is the point where the custom implementation becomes more than a local exception:
Those are the core responsibilities of a CSV parser.
The custom path still has local, per-item state, so extending it to multiline records and strict EOF behavior will require moving parser state into the reader object and changing the outer iterator lifecycle.
Performance uncertainty
The common path retains
csv-core's DFA, butskipinitialspacein #8304 first quote-scans and copies the complete input beforecsv-coreparses it again.The custom path allocates per-field buffers instead of using
csv-core's contiguous output buffer and field-end offsets.This does not prove that the end-to-end implementation is slow. Python object creation may dominate total cost. It does mean that the performance benefit of keeping
csv-coreshould be measured at the RustPython API level rather than assumed fromcsv-coremicrobenchmarks.Compatibility matrix
The “RustPython proposed” column refers to the architecture direction exposed by #8304, not necessarily merged
main.csv-coreu8u8csv_core::Terminatorcsv-coreEOLskipinitialspaceQUOTE_STRINGSconversionQUOTE_NOTNULLconversionWhy an unchanged thin wrapper is insufficient
Some missing behavior can be implemented around
csv-core:The difficult cases are those where
csv-coreconsumes syntax before the wrapper can reinterpret it.Unquoted escape
In CPython, escape is recognized in both
START_FIELDandIN_FIELD.If an escaped byte is a delimiter, CR, LF, quote, or another special byte, unchanged
csv-corewill still interpret that byte according to its grammar. A wrapper would need to:csv-coreoutput for parts of a field;All of these add a second grammar and difficult state synchronization.
Strict parsing
csv-coredoes not expose enough internal state to distinguish:A wrapper can run a shadow CPython-like FSM over the raw input, but then the wrapper is implementing most of the parser grammar solely to validate another parser.
Iterator boundaries
ReadRecordResult::InputEmptydoes not tell the caller whether more Python iterator items should be fetched as part of the same record. An empty input slice means true EOF tocsv-core, while CPython requires a distinct item-boundary event.A shadow state machine can decide whether to fetch another item, but again this duplicates the parser state.
Therefore, a complete wrapper is technically possible, but it is no longer a thin adapter and may be harder to reason about than a single compatibility parser.
Architecture options
Option A: Continue the current hybrid approach
Keep
csv-corefor common cases and add custom paths or preprocessing for each unsupported feature.Pros
csv-corethroughput for inputs that avoid preprocessing and custom dispatch;Cons
Assessment
Reasonable for narrow fixes, but not a good long-term architecture if CPython compatibility remains the goal.
Option B: Keep unchanged
csv-coreplus a comprehensive wrapper/shadow FSMUse
csv-corefor output generation while a RustPython-owned FSM tracks CPython state, validates strict mode, handles iterator boundaries, and records provenance.Pros
Cons
csv-corestate prevents direct synchronization checks;Assessment
Possible, but the complexity is hidden rather than removed. A prototype should demonstrate that it is materially simpler or faster than an independent parser before choosing this option.
Option C: Implement one RustPython-owned CPython-compatible Unicode parser
Model the CPython state machine directly in Rust, operating on a Python-character representation rather than
u8.Pros
Cons
csv-corereader performance path may be lost;Assessment
The clearest route to compatibility and maintainability, but potentially the largest short-term implementation and performance risk.
Option D: Adapt, fork, or vendor
csv-corefor RustPythonAdd the missing states and metadata to the
csv-coreNFA/DFA design:skipinitialspace;The practical form could be a RustPython-specific fork, vendored code, or a substantial adapter-owned variant. An upstream contribution would only be realistic where the required changes are sufficiently general; it should not be assumed as the primary path.
Pros
Cons
csv-coreintentionally uses bytes and intentionally never reports malformed-CSV errors; CPython compatibility conflicts with both design choices;Important note
Unicode does not make a DFA impossible. A parser can classify each Python character into a small set such as delimiter, quote, escape, space, CR, LF, item-end, and other. The difficulty is that the current
csv-coreAPI and buffer model are byte-based, so reaching that design requires a substantial refactor.Assessment
Potentially the best performance/compatibility combination, but also the highest architectural and maintenance ambition. Its feasibility is uncertain enough that it should remain an option rather than the conclusion of this issue.
Option E: Use two explicit backends
Use a fast ASCII backend when a dialect is fully representable and a compatibility backend otherwise.
For example:
Pros
Cons
Assessment
A practical compromise if benchmarks show a meaningful end-to-end benefit. It should use a shared lifecycle and output contract so that only the transition engine differs.
Note on compatibility fixes during this discussion
Narrow fixes with focused tests can still be useful. The concern here is not the existence of custom code by itself, but whether larger additions continue to deepen several partially overlapping parser paths without an agreed long-term direction.
Discussion goal
The main questions for this issue are:
csv-core?This issue does not need to choose an implementation plan immediately. Reaching a shared understanding of the architectural trade-offs would already provide guidance for subsequent CSV work.
Appendix: backend-independent constraints
The following points are relevant to any replacement or dual-backend design, but they are secondary to the architecture comparison above.
QUOTE_NOTNULL,QUOTE_STRINGS, and numeric conversion.skipinitialspaceis safer as a parser transition than as a preprocessing pass that rewrites the input before another parser sees it.These constraints do not imply one specific backend. They describe compatibility requirements that Options C, D, and E would need to address, and that make Option B difficult to keep thin.
References
RustPython
csv.rscsv.writerignores a dialect's quoting and lineterminatorcsv: fix csv escape fieldsepcsv: reader allows escapechar=1csv: handle empty fields with skipinitialspaceCPython
Modules/_csv.cat v3.14.6Lib/test/test_csv.pyat v3.14.6csvdocumentationrust-csv
csv-coredocumentationcsv-core/src/reader.rscsv-core/src/writer.rs