For millions of rows, try_csv_read(text, schema, csv_opts()) from std/csv parses the whole text in one pass into typed columns — int, float, str and cat — with the core written in C: no split() and no String per field. The schema names only the columns that are used; the rest are skipped without being parsed.
// 146: CSV grande por columnas — try_csv_read, esquema y agrupación por códigos // // Para millones de filas, try_csv_read parsea en una pasada directo a columnas // tipadas (el núcleo está en C): sin split() ni un String por campo. Una columna // `cat` llega como códigos int + un diccionario, así que agrupar es indexar un // Array, sin Map. En el bench de 5M filas (2026-10-25) este programa le gana a // pandas; ver docs/design/specs/2026-10-25-csv-columnar-design.md. import "std/csv" fn main() -> int { // En la vida real: read_file("ventas.csv"). let texto: String = "id,ciudad,monto,cantidad\n1,lima,10.5,3\n2,quito,99.25,1\n3,lima,0.75,10\n4,cusco,5.0,2\n" // ¿No conoces los tipos? try_csv_guess_schema propone un esquema para revisar. match try_csv_guess_schema(texto, csv_opts()) { Result.Ok(esq) => { print("esquema propuesto: " + esq) } Result.Err(e) => { print("no se pudo proponer: " + e.msg) } } // El esquema nombra solo lo que se usa: `id` se saltea sin parsear. match try_csv_read(texto, "ciudad:cat,monto:float,cantidad:int", csv_opts()) { Result.Ok(t) => { let montos: Array<float> = t.floats("monto") let cantidades: Array<int> = t.ints("cantidad") let codigos: Array<int> = t.codes("ciudad") let ciudades: Array<String> = t.dict("ciudad") // Un acumulador por valor distinto, indexado por el código. var totales: Array<float> = [] var unidades: Array<int> = [] for c in ciudades { totales.push(0.0) unidades.push(0) } var i: int = 0 while i < t.rows() { let k: int = codigos[i] totales[k] = totales[k] + montos[i] unidades[k] = unidades[k] + cantidades[i] i = i + 1 } var k: int = 0 while k < ciudades.length() { let nombre: String = ciudades[k] print(nombre + ": " + float_to_string(totales[k]) + " (" + int_to_string(unidades[k]) + " u.)") k = k + 1 } } Result.Err(e) => { print("error: " + e.msg) return 1 } } // Un dato malo no se convierte en 0: es un Err con la fila y la columna. match try_csv_read("a,monto\n1,10\n2,diez\n", "monto:float", csv_opts()) { Result.Ok(t) => { print("no debería llegar acá") } Result.Err(e) => { print(e.msg) } } return 0 }
esquema propuesto: id:int,ciudad:str,monto:float,cantidad:int lima: 11.25 (13 u.) quito: 99.25 (1 u.) cusco: 5.0 (2 u.) csv: fila 3, columna 'monto': "diez" no es float
How it works
When the types are not known, try_csv_guess_schema proposes a schema to review. The table that comes back gives each column by name: t.floats("monto"), t.ints("cantidad"), and for the cat column t.codes("ciudad") (one integer per row) and t.dict("ciudad") (the distinct values). Grouping is then one accumulator per distinct value, indexed by the code — no Map in the loop.
The reader is strict: a bad value is not turned into a zero but into an Err that names the row and the column. Quotes, line breaks inside quotes, CRLF and a BOM follow RFC 4180. In the release notes for 0.38.0, grouping and filtering a CSV of 5 million rows with this reader took 0.93 s, against 1.21 s for pandas.