Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to match on an Option inside an Arc?

Tags:

rust

The docs for Arc<T> say:

Arc<T> automatically dereferences to T (via the Deref trait), so you can call T's methods on a value of type Arc<T>.

But is there any way to allow for matching on Option-al types?

Here is a simple example:

use std::sync::Arc;

fn main() {
    let foo: Arc<Option<String>> = Arc::new(Some("hello".to_string()));

    if foo.is_some() {
        println!("{}", foo.unwrap());
    }

    match foo {
        Some(hello) => {
            println!("{}", hello);
        }
        None => {}
    }
}

The compiler error is:

error[E0308]: mismatched types
  --> src/main.rs:11:9
   |
11 |         Some(hello) => {
   |         ^^^^^^^^^^^ expected struct `std::sync::Arc`, found enum `std::option::Option`
   |
   = note: expected type `std::sync::Arc<std::option::Option<std::string::String>>`
              found type `std::option::Option<_>`

error[E0308]: mismatched types
  --> src/main.rs:14:9
   |
14 |         None => {}
   |         ^^^^ expected struct `std::sync::Arc`, found enum `std::option::Option`
   |
   = note: expected type `std::sync::Arc<std::option::Option<std::string::String>>`
              found type `std::option::Option<_>`
like image 583
marathon Avatar asked Jan 27 '18 01:01

marathon


Video Answer


2 Answers

No, you cannot match on an Option inside of an Arc. To use a type in pattern matching, the implementation of the type must be available to you, but the implementation of Arc is not public.


In certain cases, you can perform some kind of conversion to be able to match on a reference.

For example, since Arc<T> implements Deref, you can use the * operator to dereference through the Arc<T> to the underlying T. Since there's some ergonomic syntax for this kind of matching, you can then take a reference to the value inside the Option without taking ownership of it:

match *foo {
    Some(ref hello) => {
        println!("{}", hello);
    }
    None => {}
}

You can also use Option::as_ref to convert the &Option<T> (automatically dereferenced from the Arc<T> via Deref) to an Option<&T>:

match Option::as_ref(&foo) {
    Some(hello) => {
        println!("{}", hello);
    }
    None => {}
}

Unfortunately, you can't just call .as_ref() because the trait method AsRef::as_ref takes precedence.

In both cases, it's more idiomatic to use if let if you only care about one of the match arms:

if let Some(ref hello) = *foo {
    println!("{}", hello);
}
like image 163
Shepmaster Avatar answered Oct 07 '22 11:10

Shepmaster


The first println passed the early stages of the compiler, but it got flagged by the borrow checker in a later stage. The second println was an easier fix.

use std::sync::Arc;

fn main() {
    let foo: Arc<Option<String>> = Arc::new(Some("hello".to_string()));

    if foo.is_some() {
        let f1: &Option<String> = foo.as_ref();
        let f2: Option<&String> = f1.as_ref();
        let f3: &String = f2.unwrap();
        println!("{}", f3);

        println!("{}", foo.as_ref().as_ref().unwrap())
    }

    match *foo {
        Some(ref hello) => {
            println!("{}", hello);
        }
        None => {}
    }
}

The first println confusingly uses two as_ref() method calls. The first as_ref acts on the Arc, with type signature Fn(&Arc<Option<String>>) -> &Option<String>. The second one acts on the Option, with type signature Fn(&Option<String>) -> Option<&String>

playground

like image 35
NovaDenizen Avatar answered Oct 07 '22 11:10

NovaDenizen