Buffer stream problem

I’ve been a C++ user for about 5 years now. After finally getting Rust installed, I fine language somewhat challenging in respect to reading data bases with a buffer stream, which is a string. Its hard to control the stream to extract numeral data from it like in C++. What text books are out there for beginners in Rust that supports the opening files for numeral data?

It’s not clear what you are asking for. Perhaps “buffer stream” is a term from C++? Or do you mean a Rust BufRead/BufReader? In either case, it would help to give an example of the data you want to parse, so that we can think about how best to parse it.

I believe they're referring to the way you can parse streams in C++: you simply redirect the stream into your variable and it transparently parses it, like age << input_stream (pardon my French C++).

In Rust, I'm not sure there's a comparable method. You usually have to do the splitting manually before you can parse out each datum. If you could give a more concrete example of some C++ code you'd like to emulate, we could give an idiomatic Rust solution.

1 Like

Reading a file from the hard drive

Do you know of any good text books in Rust that I can order?

Reading a file is trivial, it's what you do with the data that can be difficult. If you could be more specific about the problem or problems you're having, we could help more.

I haven't read any books on Rust except "the" Book, and that's what I'd suggest you start with. I've also heard people recommend "Rust for Rustaceans" once you've got a grasp on the language. Rustlings is also helpful. If you need more recommendations, there are a large number of posts (on this forum and others) providing links and descriptions for all sorts of learning materials; I'd recommend searching for those.

2 Likes

Sorry for not being more specific. Being new to Rust it is a bit of challenge trying to convert string to numerical data for the first time. More reading and experimenting, thank you for your input.

TL;DR Use FromStr, parser combinator library, or serde.

The most basic approach to parsing strings in Rust is to use FromStr trait:

pub trait FromStr {
    type Err;

    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

It is implemented for many primitive types (and also for more complex types, like socket addresses). And you can of course implement it for your custom types.

For example following C++ code:

int process(const std::string& input) {
    std::istringstream iss{input};

    int a, b;
    iss >> a >> b;

    return a + b;
}

can be translated to Rust in following way:

pub fn process(input: &str) -> i32 {
    let mut numbers = input.split_whitespace();

    // Takes next word and parses it as a number.
    // If there are no more words left, or parsing failed,
    // choose 0 as the default.
    let a = numbers.next().and_then(|n| i32::from_str(n).ok()).unwrap_or(0);
    let b = numbers.next().and_then(|n| i32::from_str(n).ok()).unwrap_or(0);

    a + b
}

See full interactive example here.

Rust version is more explicit, because it forces you to handle missing tokens and parsing failures yourself, but the main idea is the same. Split input by whitespaces and parse each segment as a number.

This manual processing usually doesn't scale well, so as a next step I would recommend using parser combinator library, like nom. For our example you could define a parser:

use nom::{
    character::complete::{i32 as parse_i32, multispace0, multispace1},
    combinator::{all_consuming, map, opt},
    sequence::{delimited, pair, preceded},
    Parser,
};

fn parser<'a>() -> impl Parser<&'a str, Output = i32, Error = nom::error::Error<&'a str>> {
    all_consuming(delimited(
        multispace0,
        map(
            pair(opt(parse_i32), opt(preceded(multispace1, parse_i32))),
            |(a, b)| a.unwrap_or(0) + b.unwrap_or(0),
        ),
        multispace0,
    ))
}

and invoke it with an input:

pub fn process(input: &str) -> i32 {
    parser().parse(input).map(|(_, sum)| sum).unwrap_or(0)
}

This is great for custom serialization formats. However if you are using some well known format like json, yaml, toml consider using serde which will allow you to define data format using standard structures, and will automatically generate parsers for a dozen of serialization formats. For example given following json:

{
    "name": "John",
    "last-name": "Doe",
    "age": 42
}

You can write:

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Person {
    name: String,
    last_name: String,
    age: u32
}

fn main() {
    let input = "...";
    let person: Person = serde_json::from_str(input).expect("failed to deserialize");
    
    println!("{person:?}");
}

Consider also proconio - Rust crate (it has optimizations for competitive programming[1], so beware of technicalities; there is a way to read from a file though):

use proconio::input;

input! {
    n: usize,
    m: usize,
    a: [[i32; n]; m] // `a` is Vec<Vec<i32>>, (m, n)-matrix.
}

  1. Regardless of the fact that not all judge environments bundle it, or any external crates at all. ↩︎

If I understand fine you want to do something like cin but it is very different, if you wanna you the OS functions only works with unsafe code, if you waant to use safe code it is like this

use std::io; // Import the standard I/O library

fn main() {
    println!("Please enter a number:");

    let mut input_text = String::new(); // Create a mutable, empty string

    io::stdin() // Get the standard input stream
        .read_line(&mut input_text) // Read a line into the string
        .expect("Failed to read line"); // Handle potential errors

    // Trim whitespace (like newline characters) and parse the string to an integer
    let number: i32 = input_text.trim().parse()
        .expect("Input is not a valid number"); // Handle parsing errors

    println!("You entered the number: {}", number);
}

first you store the text in a String, then your parse it to obtain a valid number, this is important, rust don't have exceptions