Does Rust intentionally prevent side effects calculating parameters?

I have the following code

fn main() -> Result<(), Box<dyn std::error::Error>> {
    const RADIX: u32 = 10;
    let mut chars = "2054".chars();
    let start = chars
        .next()
        .ok_or("no elements")?
        .to_digit(RADIX)
        .ok_or("not a digit")?;
    println!(
        "{}",
        chars.fold(start, |a, c| a * 10 + c.to_digit(RADIX).unwrap_or_default())
    );
    Ok(())
}

If I try to calculate first parameter (start) at the fold call, Rust offers me only cloning the iterator, and I am getting incorrect result. Is there some way to avoid that, or it's Rust specific and I can't do much with that?

The reason why you can't write chars.fold(chars.next()...) is the same as in this other recent thread, Why does this borrow last for whole statement? which contains several explanations. The use of chars is an expression that needs to be evaluated, and when the first one is evaluated, the value is moved out of the variable into temporary storage until the fold() call can happen, so it is not available for use again by .next().

Do you really need to treat the first digit differently from other digits? If not, then I would suggest a for loop (so that the error can be produced on any digit):

fn main() -> Result<(), Box<dyn std::error::Error>> {
    const RADIX: u32 = 10;
    let string = "2054";
    let mut a = 0;
    for c in string.chars() {
        a = a * 10 + c.to_digit(RADIX).ok_or("not a digit")?;
    }
    println!("{a}");
    Ok(())
}
3 Likes

Thank you for the explanation. Actually, it was what I needed. Regarding the code, I can remain on the functional approach just considering initial value as 0.

2054".chars().fold(0, |a, c| a * 10 + c.to_digit(RADIX).unwrap_or_default())

just be careful that starting with 0 will give you 0 for the empty string which might not be what you desire.

It's a good remark we solve like,

fn to_number(chars: &str) -> Option<u32> {
    if chars.is_empty() {
        None
    } else {
        chars
            .chars()
            .try_fold(0, |a, c| c.to_digit(10).and_then(|d| Some(d + a * 10)))
    }
}

Why not just using the following (with some adaptation) if you just want to translate number string to just number ?

"2054".parse::<u32>().unwrap()

I hope you are joking, but it's a good one.

parse is a real function, if that’s what you mean. Though, it’s worth noting that it permits a leading sign, such as "+2054". It also properly handles overflows.

(Personally, I prefer explicitly stating the radix with u32::from_str_radix(_, 10).)

1 Like

Specifically: str::parse.

Otherwise, if you were aware of it, I'm also not sure what the "joke" is. More specialized parsing requirements normally means using a dedicated parser library since this isn't generally the only thing you're parsing then.

3 Likes

The thread wasn't about how to parse a string to the number. You often can't share a real code because it's under NDA, so you need to create another model example to illustrate your problem. From other side, only vibe coders relay on a sequence of calling standard functions without understanding what is inside. For example, I may need to parse a string ignoring some characters, like '74(6)77'. There is no reason to define a filter to ignore them and rebuild a clear string and then pass it to `parse'.

Sure, but then you don't actually "have some code" as your original post says, you have some code equivalent to that, and the fact you might have some more complex problem is, in fact, already implied by the conditional

Its a relevant response not worthy of your sarcasm given you both already received a specific answer to your question, and hadn't given any reason why the standard method wasn't appropriate.

Because the standard method resolves a different problem than the post asked. Otherwise I can respond to any question from this forum with some std solution arguing it works for something, so why is it bad?

Because XY problems exist, and asking if you actually want to have a simpler solution to your problem in addition to the direct answer isn't wrong, and because plenty of people aren't aware of every Rust standard API, and not everyone here happens to be aware of everything you're aware of.

5 Likes

Dude I know english isn't your main mode of communication and that's fine.
However a lot of your posts are written in a way that invite misinterpretations of your goals due to that.
Especially if you write pseudo code that does a bad job of representing your actual dilemma.

That sinde remark and following tangent was absolutely uncalled for.
And also sorry, the NDA is a cop out.

Oh and on that topic filtering and not helpful for you but maybe others, Rust has retain:

s.retain(|c| !r#"(),".;:'"#.contains(c)).parse()?;

Would be a legit approach without allocating.

2 Likes