Struct polars_io::csv::write::Writer [−]
pub struct Writer<W> where
W: Write, { /* fields omitted */ }
csv-file
and io_csv_write
only.Expand description
A already configured CSV writer.
A CSV writer takes as input Rust values and writes those values in a valid CSV format as output.
While CSV writing is considerably easier than parsing CSV, a proper writer will do a number of things for you:
- Quote fields when necessary.
- Check that all records have the same number of fields.
- Write records with a single empty field correctly.
- Automatically serialize normal Rust types to CSV records. When that type is a struct, a header row is automatically written corresponding to the fields of that struct.
- Use buffering intelligently and otherwise avoid allocation. (This means that callers should not do their own buffering.)
All of the above can be configured using a
WriterBuilder
.
However, a Writer
has a couple of convenience constructors (from_path
and from_writer
) that use the default configuration.
Note that the default configuration of a Writer
uses \n
for record
terminators instead of \r\n
as specified by RFC 4180. Use the
terminator
method on WriterBuilder
to set the terminator to \r\n
if
it’s desired.
Implementations
Build a CSV writer with a default configuration that writes data to the given file path. The file is truncated if it already exists.
If there was a problem opening the file at the given path, then this returns the corresponding error.
Example
use std::error::Error;
use csv::Writer;
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_path("foo.csv")?;
wtr.write_record(&["a", "b", "c"])?;
wtr.write_record(&["x", "y", "z"])?;
wtr.flush()?;
Ok(())
}
pub fn from_writer(wtr: W) -> Writer<W>
pub fn from_writer(wtr: W) -> Writer<W>
Build a CSV writer with a default configuration that writes data to
wtr
.
Note that the CSV writer is buffered automatically, so you should not
wrap wtr
in a buffered writer like io::BufWriter
.
Example
use std::error::Error;
use csv::Writer;
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(&["a", "b", "c"])?;
wtr.write_record(&["x", "y", "z"])?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "a,b,c\nx,y,z\n");
Ok(())
}
Serialize a single record using Serde.
Example
This shows how to serialize normal Rust structs as CSV records. The
fields of the struct are used to write a header row automatically.
(Writing the header row automatically can be disabled by building the
CSV writer with a WriterBuilder
and
calling the has_headers
method.)
use std::error::Error;
use csv::Writer;
use serde::Serialize;
#[derive(Serialize)]
struct Row<'a> {
city: &'a str,
country: &'a str,
// Serde allows us to name our headers exactly,
// even if they don't match our struct field names.
#[serde(rename = "popcount")]
population: u64,
}
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.serialize(Row {
city: "Boston",
country: "United States",
population: 4628910,
})?;
wtr.serialize(Row {
city: "Concord",
country: "United States",
population: 42695,
})?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "\
city,country,popcount
Boston,United States,4628910
Concord,United States,42695
");
Ok(())
}
Rules
The behavior of serialize
is fairly simple:
-
Nested containers (tuples,
Vec
s, structs, etc.) are always flattened (depth-first order). -
If
has_headers
istrue
and the type contains field names, then a header row is automatically generated.
However, some container types cannot be serialized, and if
has_headers
is true
, there are some additional restrictions on the
types that can be serialized. See below for details.
For the purpose of this section, Rust types can be divided into three categories: scalars, non-struct containers, and structs.
Scalars
Single values with no field names are written like the following. Note that some of the outputs may be quoted, according to the selected quoting style.
Name | Example Type | Example Value | Output |
---|---|---|---|
boolean | bool | true | true |
integers | i8 , i16 , i32 , i64 , i128 , u8 , u16 , u32 , u64 , u128 | 5 | 5 |
floats | f32 , f64 | 3.14 | 3.14 |
character | char | '☃' | ☃ |
string | &str | "hi" | hi |
bytes | &[u8] | b"hi"[..] | hi |
option | Option | None | empty |
option | Some(5) | 5 | |
unit | () | () | empty |
unit struct | struct Foo; | Foo | Foo |
unit enum variant | enum E { A, B } | E::A | A |
newtype struct | struct Foo(u8); | Foo(5) | 5 |
newtype enum variant | enum E { A(u8) } | E::A(5) | 5 |
Note that this table includes simple structs and enums. For example, to serialize a field from either an integer or a float type, one can do this:
use std::error::Error;
use csv::Writer;
use serde::Serialize;
#[derive(Serialize)]
struct Row {
label: String,
value: Value,
}
#[derive(Serialize)]
enum Value {
Integer(i64),
Float(f64),
}
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.serialize(Row {
label: "foo".to_string(),
value: Value::Integer(3),
})?;
wtr.serialize(Row {
label: "bar".to_string(),
value: Value::Float(3.14),
})?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "\
label,value
foo,3
bar,3.14
");
Ok(())
}
Non-Struct Containers
Nested containers are flattened to their scalar components, with the exception of a few types that are not allowed:
Name | Example Type | Example Value | Output |
---|---|---|---|
sequence | Vec<u8> | vec![1, 2, 3] | 1,2,3 |
tuple | (u8, bool) | (5, true) | 5,true |
tuple struct | Foo(u8, bool) | Foo(5, true) | 5,true |
tuple enum variant | enum E { A(u8, bool) } | E::A(5, true) | error |
struct enum variant | enum E { V { a: u8, b: bool } } | E::V { a: 5, b: true } | error |
map | BTreeMap<K, V> | BTreeMap::new() | error |
Structs
Like the other containers, structs are flattened to their scalar components:
Name | Example Type | Example Value | Output |
---|---|---|---|
struct | struct Foo { a: u8, b: bool } | Foo { a: 5, b: true } | 5,true |
If has_headers
is false
, then there are no additional restrictions;
types can be nested arbitrarily. For example:
use std::error::Error;
use csv::WriterBuilder;
use serde::Serialize;
#[derive(Serialize)]
struct Row {
label: String,
values: Vec<f64>,
}
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = WriterBuilder::new()
.has_headers(false)
.from_writer(vec![]);
wtr.serialize(Row {
label: "foo".to_string(),
values: vec![1.1234, 2.5678, 3.14],
})?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "\
foo,1.1234,2.5678,3.14
");
Ok(())
}
However, if has_headers
were enabled in the above example, then
serialization would return an error. Specifically, when has_headers
is
true
, there are two restrictions:
-
Named field values in structs must be scalars.
-
All scalars must be named field values in structs.
Other than these two restrictions, types can be nested arbitrarily. Here are a few examples:
Value | Header | Record |
---|---|---|
(Foo { x: 5, y: 6 }, Bar { z: true }) | x,y,z | 5,6,true |
vec![Foo { x: 5, y: 6 }, Foo { x: 7, y: 8 }] | x,y,x,y | 5,6,7,8 |
(Foo { x: 5, y: 6 }, vec![Bar { z: Baz(true) }]) | x,y,z | 5,6,true |
Foo { x: 5, y: (6, 7) } | error: restriction 1 | 5,6,7 |
(5, Foo { x: 6, y: 7 } | error: restriction 2 | 5,6,7 |
(Foo { x: 5, y: 6 }, true) | error: restriction 2 | 5,6,true |
pub fn write_record<I, T>(&mut self, record: I) -> Result<(), Error> where
I: IntoIterator<Item = T>,
T: AsRef<[u8]>,
pub fn write_record<I, T>(&mut self, record: I) -> Result<(), Error> where
I: IntoIterator<Item = T>,
T: AsRef<[u8]>,
Write a single record.
This method accepts something that can be turned into an iterator that
yields elements that can be represented by a &[u8]
.
This may be called with an empty iterator, which will cause a record terminator to be written. If no fields had been written, then a single empty field is written before the terminator.
Example
use std::error::Error;
use csv::Writer;
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(&["a", "b", "c"])?;
wtr.write_record(&["x", "y", "z"])?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "a,b,c\nx,y,z\n");
Ok(())
}
pub fn write_byte_record(&mut self, record: &ByteRecord) -> Result<(), Error>
pub fn write_byte_record(&mut self, record: &ByteRecord) -> Result<(), Error>
Write a single ByteRecord
.
This method accepts a borrowed ByteRecord
and writes its contents
to the underlying writer.
This is similar to write_record
except that it specifically requires
a ByteRecord
. This permits the writer to possibly write the record
more quickly than the more generic write_record
.
This may be called with an empty record, which will cause a record terminator to be written. If no fields had been written, then a single empty field is written before the terminator.
Example
use std::error::Error;
use csv::{ByteRecord, Writer};
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_byte_record(&ByteRecord::from(&["a", "b", "c"][..]))?;
wtr.write_byte_record(&ByteRecord::from(&["x", "y", "z"][..]))?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "a,b,c\nx,y,z\n");
Ok(())
}
Write a single field.
One should prefer using write_record
over this method. It is provided
for cases where writing a field at a time is more convenient than
writing a record at a time.
Note that if this API is used, write_record
should be called with an
empty iterator to write a record terminator.
Example
use std::error::Error;
use csv::Writer;
fn example() -> Result<(), Box<dyn Error>> {
let mut wtr = Writer::from_writer(vec![]);
wtr.write_field("a")?;
wtr.write_field("b")?;
wtr.write_field("c")?;
wtr.write_record(None::<&[u8]>)?;
wtr.write_field("x")?;
wtr.write_field("y")?;
wtr.write_field("z")?;
wtr.write_record(None::<&[u8]>)?;
let data = String::from_utf8(wtr.into_inner()?)?;
assert_eq!(data, "a,b,c\nx,y,z\n");
Ok(())
}
Flush the contents of the internal buffer to the underlying writer.
If there was a problem writing to the underlying writer, then an error is returned.
Note that this also flushes the underlying writer.
pub fn into_inner(self) -> Result<W, IntoInnerError<Writer<W>>>
pub fn into_inner(self) -> Result<W, IntoInnerError<Writer<W>>>
Flush the contents of the internal buffer and return the underlying writer.