Trying & failing to make a simple generic function to validate numbers


#1

I have a non-generic function that works fine:

fn valid_i32(s: &str, minimum: i32, maximum: i32,
             what: &str) -> Result<i32> {
    let n: i32 = s.parse()?;
    if minimum <= n && n <= maximum {
        return Ok(n);
    }
    bail!("{} invalid or out of range", what)
}

But when I try to make a generic version that will work with any kind of number I keep getting problems. Here’s the function in a complete tiny program:

#![recursion_limit = "1024"]
#[macro_use] extern crate error_chain;
extern crate num;

mod errors { error_chain!{ foreign_links {
            Int(::std::num::ParseIntError); } } }
use errors::*;
use std::cmp;
use std::str;

quick_main!(run);

fn run() -> Result<()> {
    let a = valid_num("123", 0, 255, "a");
    let b = valid_num("245", -20, 20, "b");
    println!("a={:?} b={:?}", a, b);
    Ok(())
}

fn valid_num<T>(s: &str, minimum: T, maximum: T, what: &str) -> Result<T>
        where T: num::Num + cmp::PartialOrd + Copy + str::FromStr {
    let n: T = s.parse()?;
    if minimum <= n && n <= maximum {
        return Ok(n);
    }
    bail!("{} invalid or out of range", what)
}

And here’s the error message:

   Compiling validint v0.1.0 (file:///home/mark/validint)
error[E0277]: the trait bound `errors::Error: std::convert::From<<T as std::str::FromStr>::Err>` is not satisfied
  --> src/main.rs:22:16
   |
22 |     let n: T = s.parse()?;
   |                ^^^^^^^^^^ the trait `std::convert::From<<T as std::str::FromStr>::Err>` is not implemented for `errors::Error`
   |
   = help: consider adding a `where errors::Error: std::convert::From<<T as std::str::FromStr>::Err>` bound
   = note: required by `std::convert::From::from`

I tried it without error-chain but that was much worse. In general I find that error-chain works really well: providing you get the foreign-links right. And often I can’t figure out what they should be.


#2

You need to add this to the valid_num signature:

fn valid_num<T>(s: &str, minimum: T, maximum: T, what: &str) -> Result<T>
where
    T: num::Num + cmp::PartialOrd + Copy + str::FromStr,
    errors::Error: From<T::Err>, <== THIS LINE

#3

@vitalyd: thank you that worked superbly (full code below for anyone interested).

I’ve been learning (at least trying to learn) Rust since December. I’ve read the Crab book once & am re-reading it now. (I prefer to learn from physical books.) I’ll buy the official book as soon as it comes out (May I think). But I just don’t know why the bit you added was needed in that way. Why wasn’t it + like the other ones. And where did that whole syntax come from. Anyway, thank you once again for your help!

#![recursion_limit = "1024"]
#[macro_use] extern crate error_chain;
extern crate num;

mod errors {
    error_chain!{
        foreign_links {
            Int(::std::num::ParseIntError);
            Real(::std::num::ParseFloatError);
        }
    }
}
use errors::*;
use std::cmp;
use std::str;

quick_main!(run);

fn run() -> Result<()> {
    let a = valid_num("123", 0, 255, "a");
    let b = valid_num("245", -20, 20, "b");
    let c = valid_num("11.54", -20.0, 20.0, "c");
    println!("a={:?} b={:?} c={:?}", a, b, c);
    Ok(())
}

fn valid_num<T>(s: &str, minimum: T, maximum: T, what: &str) -> Result<T>
        where T: num::Num + cmp::PartialOrd + Copy + str::FromStr,
                 errors::Error: From<T::Err> {
    let n: T = s.parse()?;
    if minimum <= n && n <= maximum {
        return Ok(n);
    }
    bail!("{} invalid or out of range", what)
}

#4

@mark, no problem.

In order for the ? to work, you must be able to convert T::Err (i.e. its associated Err type in the FromStr impl) into errors:Error; T::Err comes out of the parse() call there. That’s exactly what the added constraint does.

Is that clear? Or did I miss the thrust of your question?


#5

I can understand the need for the extra constraint. But I don’t really see why it isn’t added using + like the other constraints.
Thanks.


#6

Because it’s a constraint on a different type, not T itself.


#7

BTW I recommend the oreilly Programming Rust book - I think that’s the best one right now.


#8

@vitaly: yes, that’s the Crab book that I’m reading!

Ah, I see that the constraint is on the error return value.

I have made more progress thanks to your help. But there is one more step I’d like to take:

#![recursion_limit = "1024"]
#[macro_use] extern crate error_chain;
extern crate num;

mod errors {
    error_chain!{
        foreign_links {
            Int(::std::num::ParseIntError);
            Real(::std::num::ParseFloatError);
        }
    }
}
use errors::*;
use std::cmp;
use std::fmt;
use std::str;

quick_main!(run);

fn run() -> Result<()> {
    let a = valid_num("123", 0, 255, "a");
    println!("a={}", a.unwrap());
    match valid_num("245", -20, 20, "b") {
        Ok(b) => println!("b={}", b),
        Err(err) => {
            match Error::from(err) {
                Error(ErrorKind::Int(err), _) => println!("b={}", err),
                err => println!("err: {}", err)
            }
        }
    }
    let c = valid_num("11.54", -20.0, 20.0, "c");
    println!("c={}", c.unwrap());
    let d = valid_num("5.x", -20.0, 20.0, "d");
    println!("d={}", d.unwrap_err());
    Ok(())
}

fn valid_num<T>(s: &str, minimum: T, maximum: T, what: &str) -> Result<T>
        where T: num::Num + cmp::PartialOrd + Copy + str::FromStr +
                 fmt::Display, errors::Error: From<T::Err> {
    let n: T = s.parse()?; // How to I add: format!(" for {}", what)
    if minimum <= n && n <= maximum {
        return Ok(n);
    }
    bail!("{} must be in range [{}, {}]", what, minimum, maximum)
}

I added another constraint fmt::Display so that I can now include the minimum & maximum in error messages.

But what I can’t figure out how to do is to add a bit of text to the error message produced when str::parse() fails. I’ve tried doing .chain_err(|| ...) but that won’t compile.

Thanks.


#9

Just to mention, I’ve now refined the valid_num() function down to this:

fn valid_num<T>(s: &str, minimum: T, maximum: T, what: &str) -> Result<T>
        where T: num::Num + cmp::PartialOrd + Copy + str::FromStr +
                 fmt::Display {
    match s.parse() {
        Ok(n) if minimum <= n && n <= maximum => Ok(n),
        Ok(_) | Err(_) => bail!("{} must be a number in range [{}, {}]",
                                what, minimum, maximum)
    }
}

This parses the string into a number and gives a nice error if the string doesn’t parse or if it does but the value is out of range.


#10

To use chain_err you need to add another constraint (bound) on T::Err to say it’s an std::error::Error + Send: play


#11

Thanks for that. I am keeping your code for reference. In this case I’ll use my simpler code, but I’ll keep yours to see how to use error chain on ‘foreign’ errors.
Thanks again.