Closed llogiq closed 5 years ago
Suggesting an enum instead?
I dunno, the few times I've seen it used are when you have a few option-using APIs being stacked up.
Nested options are a unary counting system. But what does the presence of the absence of a value mean? (apart from: Your program should be refactored)
Sure.
But there are plenty of std APIs that deal with options, and there could be legitimate use cases for using nested options in case one is composing such APIs.
Though I'm okay with linting on AST Option<Option<T>>
s. My concern is mostly with expr_ty
nested options.
It happens (with the comment in libsyntax providing one rationale):
$ ag -Q 'Some(None)'
src/libcore/option.rs
933: Some(None) => {
src/librustdoc/markdown.rs
63: markdown::PLAYGROUND_KRATE.with(|s| { *s.borrow_mut() = Some(None); });
src/libsyntax/ext/format.rs
264: /// yet, `Some(None)` means that we've seen the argument, but no format was
269: /// that: `Some(None) == Some(Some(x))`
src/libsyntax/parse/parser.rs
4122: Some(None) => true,
In my code I have an enum for "sensor currently in use" and None is one of the options, so I have a Some(None)
which means "I succeeded in checking the sensors, and none of them are in use". Should I be refactoring this?
We should probably make this more clear: This lint should not catch Some(None)
. It should catch Option<Option<..>>
types in function signatures and perhaps typedefs. To correctly match Option::Some(Option::None)
would require expr_ty
, as Manish noted above.
Though I'm okay with linting on AST Option<Option
>s. My concern is mostly with expr_ty nested options.
So basically if there's a literal Option<Option<_>>
ast::Ty
node (not some expression of the type Option<Option<_>>
, we lint). We allow it in typedefs.
This can perhaps be done cleanly in check_ty
, using ast_map
s parenting to check if we're in a typedef.
I'd suggest we allow nested options in typedefs actually, and disallow elsewhere.
Oh! I think we have a valid use-case for this in Diesel: Updating or skipping nullable fields (these in-progress docs for AsChangeset
show how it's used). None
doesn't update the field, Some(None)
sets it to NULL
in the database, and Some(Some(x))
sets it to x
.
I know this is an edge case, but just wanted to give you a heads up as it's an edge case that we can predict some diesel users will encounter :)
cc @mikeritewho wrote a PR to add this lint
@killercup wouldn't an enum be better here?
enum DbUpdate<T> {
Keep,
Clear,
Update(T),
}
I mean, you just had to explain to us what each of the three cases of the nested Options does ;) So that's 3 variants... which is what an enum is perfect for, right?
This, or part of it, was implemented in #2299. Is there still work to be done? Maybe this issue can be closed :)
enum Updater<T> {
Keep,
Clear,
Update(T),
}
I'm wondering if there is a standard type of this kind in the standard library. Right now, I'm forced to use Option<Option<T>>
to keep the three states: replace, no-change and clear.
It will be super easy to create one myself, obviously. However, is that a good practice to constantly re-invent the wheel? And to re-implement all the nice support methods defined for Option
...
Nested options are a unary counting system. But what does the presence of the absence of a value mean? (apart from: Your program should be refactored)
It means exactly as it means: There is a value present, and it is nothing.
Option<Option<T>>
is crucial to formulate tri-state values, such as:
Think of how many three-state concepts you find in a typical system and you'll know that it is a big error not to have a tri-state enum built into Rust.
Each tristate has different meanings though. In each of the examples you gave you really need a custom enum for it to be clear
Each tristate has different meanings though. In each of the examples you gave you really need a custom enum for it to be clear
Yes, a custom enum will work just fine. However, I'll be giving up the whole set of nice useful utility methods defined on the Option
, and even on the Option<Option>>
like flatten
.
Or I can just copy/paste the Option
source code. Neither way is very elegant.
I have thought about making a custom enum and then DeRef
it to Option<Option>>
... but I can't figure out how to return an &Option<Option>
on the deref
method form the enum.
The combinator method names also don't make as much sense in tristate contexts. Its not clear at all, you really should use a custom enum with well named method. Clippy is pushing you towards the right thing here.
(Deref won't work, it has to produce references and you don't have actual Options to borrow from. Furthermore, using deref for custom coercions is a bit of an antipattern)
you really should use a custom enum
I suppose so. But if everybody does that, won't every program be littered with a bunch of similar-usage types that do similar things?
A Tri-State enum comes up so often that there really should at least be a standard crate to call upon...
My personal concern mentioned above was based on the fact that a lot of code generation was involved, and I've come around over the last two years.
But if everybody does that, won't every program be littered with a bunch of similar-usage types that do similar things?
Similar, but not identical. If you wanted to have shared behavior for some part of it, you might want to have a derive macro that allows you to flag one variant as "empty" or one as "full" and get some methods like unwrap
for free.
But I would not shy away from writing new "simple" types like these. They tend to make the code so much more readable that the small amount of needed boilerplate is – IMHO – quite acceptable. It is kinda the same as using custom enums instead of bool
all over the place, just one step more abstract :)
Again, they aren't the same enum, the implications of each state are different in each case, unifying them leads to a loss of clarity. I've come across this situation a bunch and it's been exceedingly rare where some generic tristate would have led to more clarity.
Put a different way: we use C-like enums a ton, "why don't we just use integers instead?". Just because things are represented the same doesn't mean they're logically the same
I don't know if it's used anywhere, but it does seem like misuse.