-
Notifications
You must be signed in to change notification settings - Fork 856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nil
or null
values
#30
Comments
you don't need nil or null, just leave out that assignment. |
Yeah, I'm not convinced of the usefulness of nil. TOML is intended for configuration, at which point @aaronblohowiak is right: just leave it out. I'm open to use cases and further convincing, though. |
no further arguments here. I think it might be important to implementers to know if an empty "key group" should result in a key with no value (ie. nil or null depending on the language) or no key at all |
@benolee Ah, good question. I'm going to say it should be an empty hash table. |
It's possible I'm using TOML for the wrong thing here, but I was going to use TOML as a bridge for query languages in my CLInvoice crate since I already have The reason for this is that CLInvoice is designed to be able to handle any permanent storage facility, whether or not it actually has a Structured Query Language of its own. Because of this, I needed to create a unified query 'language' based on the model and what operations made sense for it. Writing an adapter for CLInvoice explicitly provides support for this query 'language'. Querying in CLInvoice is built on the backbone of the # rest of `InvoiceDate` query left out for simplicity's sake
[paid]
condition = 'HasAny'
value = [
2020-04-01T03:00:00Z,
None
] The above would be quivalent to the English statement "match Obviously, for
I had considered nom but writing a DSL for this project seems like it would lead to less ROI than serializing / deserializing a model + helpful errors in this case. |
This is because TOML does not support `None` in lists. We need to be able to tell the difference between `Some` and `None` in list items in order to support `Match<'_, Option<_>>`. SEE toml-lang/toml#30
@Iron-E Although there won't be a [paid]
condition = 'HasAny'
value = [
2020-04-01T03:00:00Z,
{} # This represents NULL in the value list.
] It's not a literal null, but it would do the trick. You could also use any value that isn't a datetime, like In any case, you would need to handle non-datetime values gracefully, but you would need to do that with any hypothetical |
Here's a use case: You want to read config from a TOML file, interpreting values as defaults, but you want environment variables with the same name to be able to override the defaults. An empty field thus indicates to your code that it must look in the environment, and it indicates to the user that an environment variable must be set. [config]
my_db_host = '127.0.0.1'
my_db_user = 'user'
my_db_pass |
@danhje Based on what you wrote, this use case has no real "defaults," a.k.a. values that are used in the absence of all other settings. Everything is set by environment variables first and foremost, followed by the settings in the TOML configuration file. Any missing setting must certainly lead to an error. What this use case needs is just documentation. No value, not even an explicit null, would indicate that Here's a pattern for this use case. This configuration would come with the installation for the users to fill out. All equivalent environment variable settings appear next to the configuration setting. [config]
# Environment variable settings override the values here.
# Database host (Env: MY_DB_HOST)
my_db_host = '127.0.0.1'
# Database user account (Env: MY_DB_USER)
my_db_user = 'user'
# Database password cannot be set here.
# Required Env: MY_DB_PASS |
Nobody reads documentation, and config comments are ugly. But more importantly, in my use case it’s not just about signaling to the user what variables are expected, I also want access to “empty” variables in code. Consider how docker-compose interprets empty variables to mean that the variable should be mirrored from the host’s environment. In that case, leaving out the variable or using a comment isn’t an option. Docker composes uses yaml, and my understand is that leaving the value out really just results in an empty string, not a null, which I suppose is fine for my use case. Here’s my use case, in a little bit more detail: I want to create a variable / secret managing library for Python. The library is meant to be used for app development in large teams, where it’s difficult for each developer to keep track of all the environment variables that have to be set in order for the code to work. I want the users of my library to be able to centrally manage all these variables in a config file that could either be included or excluded from version control. I want the library to be able to give a friendly warning to a developer if a variable doesn’t have a default and isn’t found in the environment. So if you as a developer pulls down some commit where a fellow developer, unbeknownst to you, have introduced new variables that need to be set, you’ll find out about it right away rather than when the app fails unexpectedly, possible with a not so helpful error. When working in interactive mode, I also want tab completion to present you with all variables from the config file, both set and unset. I could force the users of my library to list expected variables in code rather than a config file, including default values, but this breaks the separation of config and code. In a project with hundreds of code files it also makes it harder to track down those expected variables, and it’s hard to enforce a central location for them. If there’s a clever solution I haven’t thought of, I’d love to hear about it. But I think I’ll just use yaml instead. Which is a shame, since I was hoping to allow using pyproject.toml. |
@albertotb if that second example parses OK in some implementation you're using you should file a bug report because it should not |
It seems it was fixed in the latest Python implementation (0.10.2) |
Can't you just use |
… toml value cannot and won't accept null value. see toml-lang/toml#30 (comment)
* github action: fix event payload type of repository dispatch, because toml value cannot and won't accept null value. see toml-lang/toml#30 (comment) * fix repository dispatch problems. now ci::filter_workflow directly returns config::runtime::Workflow * separate run/boot handler * apply revision/silent/verbosity correctly
Question regarding this: Instead of allowing null/nil/none, can it be specified what parsers "should" do by default if they see a null/nil/none value anyway? Ie. I think the standard should recommend to simply omit them from the serialized toml document, or throw a type error, or to use a magic stringified value (id hope not), or something else (maybe putting an empty commented line with the key but no value?). This is relevant since libraries are making different decisions on this. E.g. Fatal1ty/mashumaro#85 or samuelcolvin/rtoml#23. The latter is interesting - it claims to be fully compliant and pass all the toml tests - but apparently if stringifies null values, somehow (wrongly I assume) indicating that this is the right thing to do. |
@jonaslb TOML parsers will never see a null value, since those don't exist in TOML files. What you mean is a TOML serializer/writer. About giving advice for them on how to represent types that don't map cleanly to a TOML type: I'm a bit skeptical about this since it might vary a lot on the use case. In the general case, "throw an error" is probably indeed the best course of action. But there may be applications, where, say, calling a So I think the general rule is: when writing a serializer, document how it handles unexpected types. And for TOML users: the best course of action is certainly not to pass any unexpected objects to your TOML writer in the first place. But if you want/need to do so anyway, make sure that it handles them in a way you consider appropriate. |
Here's a use case: Layered configuration with global default config (read from, say, |
The snippet you've quoted:
Was written over ten years ago. There has been considerable deliberation on this point in the intervening years (including people giving examples exactly like yours), and sentiment has coalesced pretty firmly around "nulls are bad, actually" (see discussions in #146, #802, #803, #921, #975). |
There is one good use case for nulls in TOML configuration files: sane defaults. Bear with me here. Imagine that you have a setting like #[derive(Serialize, Deserialize)]
pub struct Config {
#[serde(default = "Config::default_config_timeout")]
connect_timeout: u64,
}
impl Config {
fn default_config_timeout() -> u64 {
1000 * 30 // 30 seconds in milliseconds
}
} Everything is great and right in the world. If your users want to set it higher or lower, they can just:
and everyone is happy. But what if your users don't want a connect timeout? Their network is slow, they know it and they are in no rush, and why would they want to throw errors to their users when they know things will take a while? Their option is to either set it to a super large value like 1 year in milliseconds, which...well, works in practice, until you actually want to wait 1 year for something and Christmas day comes and your on-call gets a nasty page about an error they have never seen before, or for your software to support weird values like But what if we could set it to null instead?
means there is no connect timeout and the app should wait forever, as desired by the user. Nulls are valid values in databases, software code and life in general: they mean there is nothing here, and that's how we like it. |
Would it, though? Your software would need exactly the same amount of documentation regardless of what you chose for a sentinel, be it |
I think my main concern is using incorrect types, e.g. |
You will always need runtime logic, with or without nulls. TOML data is heterogeneous so you can't somehow get compile-time validation without doing type-based logic on lookups first. You need to explicitly specify the type of |
Serde will take care of that. By forcing me to change the data type I need to make sure that the value is valid, but before, any value was valid... if deaerialization is successful that is. So by forcing me to change the data type, I need to write more error-prone code. Whether nulls belong in the TOML spec or not I think is a question of taste to be honest. It's hard for me to know what's the right decision here since your points are valid as well and having explicit nulls in a config file looks weird. That being said, null is a valid value for a data type so excluding it from the spec is not driven by correctness but probably by ergonomics and taste which are fine choices to make but nonetheless force the user to do something the TOML way instead of the optimal way. |
Yeh, indeed it is a matter of taste. I'd like to clarify something though:
No, it isn't. It's not in the spec, so it's not a valid value. It existing conceptually, or being in other languages, doesn't confer validity in TOML. The canonical way to express (something like) 'null' in TOML is to omit a value, so you still have that option.
What the 'optimal' way is happens to be a matter of taste too, FYI. IMO the most 'optimal' thing is what requires the least expression in the TOML config file itself - hard to beat "omit this KVP entirely" there. I do recognize that the lack of null makes interop with other languages a decent bit harder in many cases, but I think it's also important to acknowledge that TOML is a config language first-and-foremost - any serialization concerns are for implementers to worry about, not users. Implementers can always jump through an extra hoop via a helper function (or similar), which is great if it keeps the language simpler for users. |
That is a flawed concept by itself which doesn't work for the described use case (user overrides global default; default is not absent).
"omit this KVP entirely" is not a universally applicable way to express "undefinition" in TOML, so cannot be a good general best practice recommendation. If there were a |
I'm sorry but I'm going to say that we're not revisiting this design choice at this point and an extended discussion about the consequences of that choice is something that I'd prefer folks have on a new discussion over on https://github.com/toml-lang/toml/discussions instead of in an issue that was closed a decade ago. |
People in the 21st century are really still arguing against the concept of zero. "It's not useful to say you have |
(moved discussion from #11)
It seems like
nil
ornull
values must be allowed. For example,in this case, it seems like it would make sense to be able to set them with the normal
key = value
syntax. Here are some alternatives for thought:The text was updated successfully, but these errors were encountered: