rustrust-cargo

match Option<> with many patterns and 1 conditional


I have a match block that determines who needs to send a message

match text.to_id {
            Some(id)  => {
                if let Some(tx) = state.read().unwrap().get(&id){
                    tx.send(message.clone()).unwrap();
                } else {continue;}
            } 
            None  => {
                for (_id, send) in state.read().unwrap().iter(){
                    send.send(message.clone()).unwrap();
                }
            }
        }

but if id == 0, this should send the message to everyone the same way. if I try:

None | Some(0)=>
None | Some(id) if id == 0=>

or

Some(id) if id !=0 =>

it doesn't work or send me a error:

`Some(_)` not covered

how can use pattern None too, if id==0?


Solution

  • Patterns are traversed top-to-bottom. So if you add alternates to the None clause (overlapping with the Some() clause) you need to move the pattern to the top:

    match text.to_id {
        None | Some(0) => {
            for (_id, send) in state.read().unwrap().iter(){
                send.send(message.clone()).unwrap();
            }
        }
        Some(id)  => {
            if let Some(tx) = state.read().unwrap().get(&id){
                tx.send(message.clone()).unwrap();
            } else {continue;}
        } 
    }
    

    If you add a guard to the original Some clause, then you still need to match Some(_) in the bottom clause for Some(0) and Rust does not have refinement types so it does not understand that the complement of Some(id) if id != 0 is Some(0), which is why it's telling you that Some(_) is not covered. In that case the easiest is to just use a wildcard for the second branch

    match text.to_id {
        Some(id) if id != 0 => {
            if let Some(tx) = state.read().unwrap().get(&id){
                tx.send(message.clone()).unwrap();
            } else {continue;}
        } 
        _ => {
            for (_id, send) in state.read().unwrap().iter(){
                send.send(message.clone()).unwrap();
            }
        }
    }
    

    An other alternative is to fold the Some(0) case into a None via Option::filter, such that the case does not "infect" the match:

    match text.to_id.filter(|id| *id != 0) {
        Some(id) => {
            if let Some(tx) = state.read().unwrap().get(&id){
                tx.send(message.clone()).unwrap();
            } else {continue;}
        } 
        None => {
            for (_id, send) in state.read().unwrap().iter(){
                send.send(message.clone()).unwrap();
            }
        }
    }