Table of contents
It has a richer set of features than JSON and less error-prone than YAML. That said, it's not free from friction. Different implementations will have their own dragons but this note focuses on an issue I repeatedly faced whilst using toml-rs.
# The date type
TOML has a built-in notation for dates and times based on the RFC3339 but making it more flexible, closer to the spirit of the ISO8601. You get to choose from Offset Date-Time, Local Date-Time, Local Date or Local Time.
Where a string would be anything surrounded by double quotes (
"), dates are pattern-based.
So, a valid TOML document would be:
= "note" = 2021-07-12 = "TOML dates" = ["rust", "standard"]
# TOML implementations in Rust
The most popular implementation in Rust is toml-rs which at the time of writing implements TOML v0.5.
There is also Taplo which implements TOML v1.0 but I haven't tried it yet so I can't compare.
# The problem
Let's represent the previous TOML document as a struct in Rust:
The first decisions to make is how to represent the date field. You could use a string, implement your own date type or, what I tend to do: use Chrono.
Chrono has a set of types to choose from, roughly speaking though, the mapping with TOML looks like:
- TOML Offset Date-Time -> Chrono
- TOML Local Date-Time -> Chrono
- TOML Local Date -> Chrono
NaiveDate. Note that
Datealso exists but is not intended to be used this way.
- TOML Local Time -> Chrono
Given that I want a date and not be bothered by the time component, I'll go with a
To implement the conversion from and to a string Serde is a safe choice:
use NaiveDate; use ;
When you try to deserialise with:
let raw r#" type = "note" publication_date = 2021-07-12 title = "TOML dates" tags = ["rust", "standard"] "#; let note: Note = from_str?;
It fails with a not so obvious message:
Error: invalid type: map, expected a formatted date string for key `publication_date`
The problem is that the toml-rs serde implementation for dates is just for the
toml::Value::Datetime. Optionally, you can compile Chrono with the
serde feature but even with that you'd be out of luck because the implementation only works for
So a simple enough option is to 1) implement your type and 2) implement the Serde deserialisation for it.
use Result; use NaiveDate; use ; use FromStr; ;
The implementation piggy-backs from toml-rs' own implementation which is likely to be what you want when implementing an efficient solution but it does the job.
With that we can now use
toml::from_str free of errors but
toml::to_string still has a quirck as it serialises
Date as a string if you choose to derive the implementation.
So, we need to implement our own:
// Date and Deserialize implementation omited. use fmt;
Effectively the inverse of what we did for
Deserialize. Now we can do a roundtrip:
let raw r#" type = "note" publication_date = 2021-07-12 title = "TOML dates" tags = ["rust", "standard"] "#; let note: Note = from_str?; let raw_again = to_string?; assert_eq!;
# What about JSON?
The previous implementation works well as long as you only care about TOML but will fail if you need to deserialize from other formats. To do that, we need a different approach: the visitor trait. Let's start from scratch:
use Result; use NaiveDate; use ; use ; use FromStr; ; ;
Not too bad. Now we don't depend on toml-rs in our implementation other than knowing that it relied on a map to identify a date.
So now we can do:
let raw r#" type = "note" publication_date = 2021-07-12 title = "TOML dates" tags = ["rust", "standard"] "#; let note: Note = from_str?; let note2: Note = from_str?; assert_eq!;
At this time though, I don't know whether it's possible to serialise differently depending on which
Serializer is driving.
# Closing thoughts
TOML is an appealing format for people that value identifying dates at source. But it imposes extra burden if you need be compatible with less expressive formats.