The thing I love about Rust (and Dart, to lesser extent), in this regard - is that this is doable in 2 minutes:
Summary
#[derive(Debug)]
enum Nullable<T> {
Null,
Value(T),
}
fn _main() {
// too horribly looking?
let f_option = Some(false);
// no worries, you can do this:
let nullable = f_option.into();
// another option?
let another_f_option = best_fn_ever(nullable);
// no problem
let n = Nullable::from(another_f_option);
// debuggable nullable, yay
println!("{:#?}", n);
}
fn best_fn_ever(b: Nullable<bool>) -> Option<String> {
let back = if let Nullable::Value(true) = b {
"life's awesome!"
} else {
"life's HORRID"
};
Some(String::from(back))
}
// THE MAGIC
impl<T> From<Nullable<T>> for Option<T> {
fn from(n: Nullable<T>) -> Self {
match n {
Nullable::Value(v) => Some(v),
Nullable::Null => None
}
}
}
impl<T> From<Option<T>> for Nullable<T> {
fn from(o: Option<T>) -> Self {
o.map(|some| Nullable::Value(some))
.unwrap_or(Nullable::Null)
}
}
You can define and implement custom IntoNullable
traits for Result and Option with a trivial nullable()
method that will always return your favorite Nullable for every Result / Option out there, wrap your own Nullable into a Result or Option for those few pesky moments when you do need to pass them as arguments to another core library - and you won't have to deal with Some
or None
ever again.
At the same time, none of the existing code that might be running and diligently servicing millions of people around the world will have to be refactored simply because you happen to be a bit more familiar with, and have a bit of a soft spot for, another syntax. It's a win-win for everyone.
(sarcasm is in my blood, don't take it personally)
Some things, ultimately, don't matter that much. What you do with those Nullable's and Option's is much more important than the exact combinations of keystrokes needed to instruct the compiler to, at the very end of it all, to do the exact same thing behind the scenes.
Same goes for panic_on_error_with
vs expect
. I, personally, hate the unwrap
word - and I couldn't, in a million years, tell you why. I also hate the Result<()>
, Ok(())
and the ()
unit themselves. They are just .. mildly annoying to look at. Guess what? Every project of mine starts with type Result<T=()> = std::io::Result<T>
and const Done: Result = Ok(())
. There. I wrote it once and I never touch it.
Then, in main()
, I can happily go:
fn main() -> Result {
...
Done
}
Because I want to, because I can, and because that's more readable for me - but would I ever ask the core team itself to get rid of unit and replace it with void
(which I do happen to like a bit better myself)? Not in a million years. It does what it's supposed to do, it's there, so leave it be.
I would think some compile-time detection of Option<T>
(or Nullable<T>
- whatever rocks your boat) in the parameter declaration of a given fn
:
fn call_me(number: u8, just_kidding: Option<bool>) { ...
In order to be able to just call_me(0)
instead of call_me(0, None)
- would be awesome. But that's a totally different matter and would require a whole new topic to discuss all the pro's and con's.
TL;DR - dude, it's just naming. Wrap it in your own custom trait
's - if you must - and forget about it