Rust pattern matching with an existing binding?

Viewed 248

I am learning Rust from yesterday. The following code is simple --

use encoding_rs::Encoding;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::option::Option;
use std::path::Path;

extern crate encoding_rs;
extern crate encoding_rs_io;

fn main() {
    let mut reader = BufReader::new(file);
    let mut bom: [u8; 3] = [0; 3];

    // read BOM
    if let Ok(_) = reader.read_exact(&mut bom) {
        // sniff BOM

        // Because Rust disallows NULLs, hence I declare `Option<&Encoding>` to store the result of encoding.
        let mut enc: Option<&Encoding> = None;
        match Encoding::for_bom(&bom) {
            Some((encoding, _)) => {
                // <-- Some((enc, _))
                enc = Some(encoding);
            }
            None => {
                if let Some(encoding) = Encoding::for_label("UTF-8".as_bytes()) {
                    enc = Some(encoding);
                }
            }
        }

        if let Some(encoding) = enc {
            println!("{:?}", encoding);
        }
    }
}

It opens a text file, and try to analyze its encoding by parsing BOM(Byte Order Marker). If Encoding::for_bom does not return an encoding, the code will take use UTF-8 as default.

I dislike unwrap() because it always assume there is a valid result

My question is : is there a way to do pattern matching and put the result directly into an existing mutable binding?

e.g. Change Some((encoding, _)) to Some((enc, _)) hence I don't need the line of enc = Some(encoding)

2 Answers

Many rust constructs can be used as expressions, i.e. they can return a value. So if every branch of your match returns a value of the same type, you can assign it directly into a variable. It does not need to be mutable unless you plan to change it later.

let mut reader = BufReader::new(file);
let mut bom: [u8; 3] = [0; 3];

if let Ok(_) = reader.read_exact(&mut bom) {
    let enc = match Encoding::for_bom(&bom) {
        Some((encoding, _)) => Some(encoding),
        None => Encoding::for_label("UTF-8".as_bytes()),
    };

    if let Some(encoding) = enc {
        println!("{:?}", encoding);
    }
}

I'd use a combination of map and or_else:

let enc = Encoding::for_bom(&bom)
    .map(|t| t.0)
    .or_else(|| Encoding::for_label ("UTF-8".as_bytes()));

Or (clearer but slightly longer):

let enc = Encoding::for_bom(&bom)
    .map(|(e, _)| e)
    .or_else(|| Encoding::for_label ("UTF-8".as_bytes()));
Related