I'm trying to write a trait that will allow me to "unwrap" multiple nested Option<Option<...<T>>>>
to a single Option<T>
to better work with an API I am using. I'm trying to create a generic solution, but I can't figure out how to make it work.
This is one of my many attempts:
trait UnwrapOption<T> {
fn unwrap_opt(self) -> Option<T>;
}
impl<T> UnwrapOption<T> for Option<T> {
fn unwrap_opt(self) -> Option<T> {
self
}
}
impl<T> UnwrapOption<T> for Option<Option<T>> {
fn unwrap_opt(self) -> Option<T> {
match self {
Some(e) => e.unwrap_opt(),
None => None,
}
}
}
fn main() {
let x = Some(Some(Some(1)));
println!("{:?}", x.unwrap_opt());
}
error[E0282]: type annotations needed
--> src/main.rs:22:24
|
22 | println!("{:?}", x.unwrap_opt());
| --^^^^^^^^^^--
| | |
| | cannot infer type for type parameter `T` declared on the trait `UnwrapOption`
| this method call resolves to `Option<T>`
If a nil optional is unwrapped, an error is thrown saying " Unexpectedly found nil while unwrapping an Optional value ." You're supposed to use forced unwrapping only in a pre-defined environment where you're certain that the optional value won't be nil. You can forcefully unwrap an optional using the exclamatory (!) operator like this:
Unwrapping in Swift is essentially verifying if the Optional value is nil or not, and then it performs a task only if it's not nil. You can perform unwrapping in the following ways: Using an if else block
NOTE: The tryParseOption code is just an example. A similar function tryParse is built into the .NET core libraries and should be used instead. Like other union types, option types have an automatically defined equality operation
The option type is widely used in the F# libraries for values that might be missing or otherwise invalid. For example, the List.tryFind function returns an option, with the None case used indicate that nothing matches the search predicate. [1;2;3;4] |> List.tryFind (fun x-> x = 3) // Some 3 [1;2;3;4] |> List.tryFind (fun x-> x = 10) // None
Instead of flattening out the nested option, as the other answer shows, I'd advocate that you never create an Option<Option<T>>
that you need to flatten in the first place. In the majority of cases I've seen, it's because someone misuses Option::map
when they should have used Option::and_then
:
fn main() {
let input = user_input();
let a = input.map(add1);
// a is Option<Option<i32>>
let b = input.and_then(add1);
// a is Option<i32>
}
fn user_input() -> Option<i32> {
Some(10)
}
fn add1(a: i32) -> Option<i32> {
Some(a + 1)
}
Remember that Rust is a statically typed language; you will always know the exact level of nesting.
See also:
I solved it with auto traits (optin_builtin_traits
), but I'm not sure if this is the best approach:
#![feature(optin_builtin_traits)]
trait IsOption {}
impl<T> IsOption for Option<T> {}
auto trait IsSingleOption {}
impl<T> !IsSingleOption for Option<Option<T>> {}
trait UnwrapOption {
type Inner;
fn unwrap_opt(self) -> Option<Self::Inner>;
}
impl<T> UnwrapOption for Option<T>
where
Self: IsSingleOption,
{
type Inner = T;
fn unwrap_opt(self) -> Option<Self::Inner> {
self
}
}
impl<T> UnwrapOption for Option<T>
where
T: IsOption + UnwrapOption,
{
type Inner = <T as UnwrapOption>::Inner;
fn unwrap_opt(self) -> Option<Self::Inner> {
match self {
Some(e) => e.unwrap_opt(),
None => None,
}
}
}
fn main() {
let x = Some(Some(Some(1)));
println!("{:?}", x.unwrap_opt());
let x = Some(1);
println!("{:?}", x.unwrap_opt());
}
playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With