Search code examples
rustinput

Rust handle input for multiple variables in a single input line


I want to read input in rust like this C++ example:

main() {
    int a,b;
    cin>>a>>b; //read user input into a and b. Can be separated by a space character or \n
    cout<<"a: "<<a<<'\n'<<"b: "<<b<<'\n';
}

input:

123 34

output:

a: 123
b: 34

Pardon me for this simple question.


Solution

  • You can write a function that parses an array of the desired amount of input values:

    use anyhow::anyhow;
    use std::fmt::Display;
    use std::io::{stdin, BufRead};
    use std::str::FromStr;
    
    pub fn read_and_parse<const SIZE: usize, T>() -> anyhow::Result<[T; SIZE]>
    where
        T: FromStr,
        <T as FromStr>::Err: Display,
    {
        let mut buffer = String::new();
        stdin().lock().read_line(&mut buffer)?;
        buffer
            .split_whitespace()
            .map(|item| item.parse())
            .collect::<Result<Vec<T>, <T as FromStr>::Err>>()
            .map_err(|e| anyhow!("{e}"))?
            .try_into()
            .map_err(|v: Vec<_>| anyhow!("invalid size: {} != {SIZE}", v.len()))
    }
    
    fn main() {
        let [a, b]: [i32; 2] = read_and_parse().expect("garbage in, garbage out");
        println!("a: {a}");
        println!("b: {b}");
    }
    

    Playground

    Without third-party crate

    You can naturally also write your own error enum, which is what anyhow spares you:

    use std::fmt::{self, Debug, Display};
    use std::io::{stdin, BufRead};
    use std::str::FromStr;
    
    #[derive(Debug)]
    pub enum ReadOrParseError<T>
    where
        T: Debug + FromStr,
        <T as FromStr>::Err: std::error::Error + 'static,
    {
        ReadError(std::io::Error),
        ParseError(<T as FromStr>::Err),
        InvalidLength(Vec<T>),
    }
    
    impl<T> Display for ReadOrParseError<T>
    where
        T: Debug + FromStr,
        <T as FromStr>::Err: std::error::Error + 'static,
    {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            match self {
                Self::ReadError(error) => write!(f, "read error: {error}"),
                Self::ParseError(error) => write!(f, "parse error: {error}"),
                Self::InvalidLength(vec) => write!(f, "invalid length: {}", vec.len()),
            }
        }
    }
    
    impl<T> std::error::Error for ReadOrParseError<T>
    where
        T: Debug + FromStr,
        <T as FromStr>::Err: std::error::Error + 'static,
    {
        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
            match self {
                Self::ReadError(error) => Some(error),
                Self::ParseError(error) => Some(error),
                Self::InvalidLength(_) => None,
            }
        }
    }
    
    impl<T> From<std::io::Error> for ReadOrParseError<T>
    where
        T: Debug + FromStr,
        <T as FromStr>::Err: std::error::Error + 'static,
    {
        fn from(error: std::io::Error) -> Self {
            Self::ReadError(error)
        }
    }
    
    pub fn read_and_parse<const SIZE: usize, T>() -> Result<[T; SIZE], ReadOrParseError<T>>
    where
        T: Debug + FromStr,
        <T as FromStr>::Err: std::error::Error + 'static,
    {
        let mut buffer = String::new();
        stdin().lock().read_line(&mut buffer)?;
        buffer
            .split_whitespace()
            .map(str::parse)
            .collect::<Result<Vec<T>, <T as FromStr>::Err>>()
            .map_err(ReadOrParseError::ParseError)?
            .try_into()
            .map_err(ReadOrParseError::InvalidLength)
    }
    
    fn main() {
        let [a, b]: [i32; 2] = read_and_parse().expect("garbage in, garbage out");
        println!("a: {a}");
        println!("b: {b}");
    }
    

    Playground